diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6657576 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +.git +.github +.venv +.pytest_cache +.ruff_cache +**/__pycache__ +**/*.pyc +apps/showcase_ui/.angular +apps/showcase_ui/dist +apps/showcase_ui/node_modules +build +dist +traces +.env +*.log diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c2f7a13 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,175 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + PYTHONUTF8: "1" + UV_NO_PROGRESS: "1" + +jobs: + python-quality: + name: Python 3.12 / ${{ matrix.os }} + runs-on: ${{ matrix.os }} + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + - windows-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Install locked development environment + run: uv sync --dev --frozen + + - name: Check Python formatting + run: uv run ruff format --check . + + - name: Lint Python + run: uv run ruff check . + + - name: Enforce quality ratchets + run: uv run python scripts/quality_ratchet.py + + - name: Type-check protected core modules + run: uv run pyright --project pyright-core.json + + # Device, cloud, manual, and end-to-end suites require dedicated + # infrastructure and intentionally do not run on GitHub-hosted runners. + - name: Run deterministic Python tests + run: >- + uv run pytest + tests/unit + tests/tools + packages/artemis-client/tests + -q + --cov=artemis + --cov=mcp_server + --cov=apps/admin_console + --cov-fail-under=60 + + frontend: + name: Frontend tests and production build + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: apps/showcase_ui + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.22.3" + cache: npm + cache-dependency-path: apps/showcase_ui/package-lock.json + + - name: Install locked frontend dependencies + run: npm ci + + - name: Run frontend tests + run: npm test -- --watch=false --browsers=ChromeHeadless + + - name: Build frontend for production + run: npm run build + + package: + name: Build and smoke-test Python packages + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + with: + enable-cache: true + cache-dependency-glob: uv.lock + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.22.3" + cache: npm + cache-dependency-path: apps/showcase_ui/package-lock.json + + - name: Build frontend assets for the wheel + run: | + npm ci --prefix apps/showcase_ui + npm run build --prefix apps/showcase_ui + + - name: Build client and Artemis distributions + run: | + uv build --package artemis-client --wheel --out-dir dist + uv build --package artemis --wheel --sdist --out-dir dist + + - name: Install wheels in a clean environment + run: | + uv venv .smoke-venv --python 3.12 + uv pip install --python .smoke-venv/bin/python dist/artemis_client-*.whl dist/artemis-*.whl + + - name: Smoke-test installed package and CLI + shell: bash + run: | + smoke_dir="$RUNNER_TEMP/artemis-wheel-smoke" + mkdir -p "$smoke_dir" + cd "$smoke_dir" + source "$GITHUB_WORKSPACE/.smoke-venv/bin/activate" + python - <<'PY' + from importlib.metadata import files, version + + import artemis + from artemis.resources import get_bundled_config_path, get_bundled_showcase_dist + + bundled_config = get_bundled_config_path("artemis.jsonc") + bundled_ui = get_bundled_showcase_dist() + assert artemis.__version__ == version("artemis") + assert bundled_config is not None and bundled_config.is_file() + assert bundled_ui is not None and (bundled_ui / "index.html").is_file() + assert not any("node_modules" in str(path) for path in (files("artemis") or ())) + print(f"Installed Artemis {artemis.__version__} with bundled runtime resources") + PY + artemis --version + artemis --help + + - name: Upload distributions + uses: actions/upload-artifact@v4 + with: + name: python-distributions + path: dist/* + if-no-files-found: error + retention-days: 7 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..dce5ba7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + - repo: local + hooks: + - id: ruff-check + name: Ruff lint + entry: uv run ruff check + language: system + types: [python] + + - id: ruff-format + name: Ruff format + entry: uv run ruff format --check + language: system + types: [python] + + - id: quality-ratchet + name: Quality debt ratchet + entry: uv run python scripts/quality_ratchet.py + language: system + pass_filenames: false diff --git a/.quality-baseline.json b/.quality-baseline.json new file mode 100644 index 0000000..5b53f91 --- /dev/null +++ b/.quality-baseline.json @@ -0,0 +1,5 @@ +{ + "broad_exception_handlers": 754, + "silent_broad_exception_handlers": 0, + "type_ignore_comments": 18 +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc3709d..7f1b8cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,10 +9,27 @@ Thank you for your interest in contributing to **ARTEMIS**! We welcome and appre - **🐛 Report Bugs & Suggest Features**: If you find an issue or have an idea to improve ARTEMIS, please open an [Issue](https://github.com/google/artemis/issues). - **💡 Submit Pull Requests**: 1. Fork the repository and create your feature/fix branch (`git checkout -b feat/my-feature`). - 2. Make your changes and verify with `make test` and `make lint`. + 2. Make your changes and verify with `make test`, `make lint`, and `make typecheck`. 3. Open a Pull Request with a brief summary of your work. - **💬 Community & Discussion**: Join our [Discord Community](https://discord.gg/wF2FN4WHGY) to chat with maintainers, ask questions, or share feedback. --- Every contribution—big or small—helps make ARTEMIS better. Thank you for building with us! + +## Test layers + +`make test` is the required, deterministic suite. It does not need an Android +device, model credentials, or the optional private cloud service. This is the +same suite used by pull-request CI. + +- `make test-integration` runs cross-component tests that may need configured + model credentials. +- `make test-device` runs Android and end-to-end tests and therefore requires + an attached, authorized device or emulator. +- `make test-all` collects every test tree and is intended for a fully + provisioned maintainer environment. + +Tests that require external state must carry the appropriate `android`, +`cloud`, `manual`, or `e2e` marker. They must remain safely collectable when +that dependency is unavailable. diff --git a/Dockerfile b/Dockerfile index 885d9b0..16e63d5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,30 +12,49 @@ # See the License for the specific language governing permissions and # limitations under the License. +FROM node:22-bookworm-slim AS frontend-builder + +WORKDIR /build/apps/showcase_ui +COPY apps/showcase_ui/package.json apps/showcase_ui/package-lock.json ./ +RUN npm ci +COPY apps/showcase_ui/angular.json apps/showcase_ui/tsconfig*.json ./ +COPY apps/showcase_ui/public/ ./public/ +COPY apps/showcase_ui/src/ ./src/ +RUN npm run build + + +FROM python:3.12-slim AS wheel-builder + +WORKDIR /build +COPY artemis/ ./artemis/ +COPY apps/ ./apps/ +COPY mcp_server/ ./mcp_server/ +COPY packages/artemis-client/ ./packages/artemis-client/ +COPY config/ ./config/ +COPY pyproject.toml setup.py README.md LICENSE ./ +COPY --from=frontend-builder /build/apps/showcase_ui/dist/ ./apps/showcase_ui/dist/ + +# Build wheels for the runtime image. +RUN python -m pip wheel --no-cache-dir --no-deps --wheel-dir /wheels ./packages/artemis-client \ + && python -m pip wheel --no-cache-dir --no-deps --wheel-dir /wheels . + + FROM python:3.12-slim WORKDIR /app -# Install necessary build tools RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ + adb \ curl \ git \ - adb \ && rm -rf /var/lib/apt/lists/* -# Copy application source code and configuration -COPY artemis/ /app/artemis/ -COPY apps/ /app/apps/ -COPY mcp_server/ /app/mcp_server/ -COPY config/ /app/config/ -COPY pyproject.toml setup.py README.md LICENSE /app/ - -# Install dependencies and artemis package -RUN pip install --no-cache-dir -e /app +COPY --from=wheel-builder /wheels/ /wheels/ +RUN pip install --no-cache-dir /wheels/artemis_client-*.whl \ + && pip install --no-cache-dir /wheels/artemis-*.whl \ + && rm -rf /wheels ENV PYTHONUNBUFFERED=1 -ENV PYTHONPATH=/app ENV ARTEMIS_APP_DIR=/app ENV PORT=8080 diff --git a/Makefile b/Makefile index c2a1b01..c3fca4a 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -.PHONY: help test install install-deps setup start ui restart stop status build-ui doctor clean precommit-install precommit lint format typecheck +.PHONY: help test test-integration test-device test-all install install-deps setup start ui restart stop status build-ui doctor clean precommit-install precommit lint format typecheck quality-ratchet help: ## Show this help message @echo 'Usage: make [target]' @@ -42,9 +42,21 @@ build-ui: ## Build the Showcase UI Angular frontend doctor: ## Run system, device, and toolchain diagnostics @uv run artemis doctor -test: ## Run all tests - @echo "🧪 Running all tests..." - @uv run pytest -v +test: ## Run deterministic tests that need no device, credentials, or private services + @echo "🧪 Running deterministic tests..." + @uv run pytest + +test-integration: ## Run non-device integration tests (may require configured model credentials) + @echo "🧪 Running integration tests..." + @uv run pytest tests/integration tests/tools -m "integration and not android and not cloud and not manual" + +test-device: ## Run Android and end-to-end tests explicitly + @echo "📱 Running device and end-to-end tests..." + @uv run pytest tests/integration tests/e2e -m "android or e2e" + +test-all: ## Run every test tree; external prerequisites must be available + @echo "🧪 Running the complete test tree..." + @uv run pytest tests packages/artemis-client/tests -m "integration or not integration" install: ## Install python dependencies via uv @echo "📦 Installing python dependencies..." @@ -66,6 +78,7 @@ lint: ## Run linting checks @echo "🔍 Running linting checks..." @uv run ruff format --check @uv run ruff check + @uv run python scripts/quality_ratchet.py format: ## Format code @echo "✨ Formatting code..." @@ -74,7 +87,10 @@ format: ## Format code typecheck: ## Run type checking @echo "🔍 Running type checks..." - @uv run pyright + @uv run pyright --project pyright-core.json + +quality-ratchet: ## Prevent broad exception and type-ignore debt from increasing + @uv run python scripts/quality_ratchet.py precommit-install: ## Install pre-commit hooks @echo "🔧 Installing pre-commit hooks..." diff --git a/README.md b/README.md index 9b47fe4..f76cf43 100644 --- a/README.md +++ b/README.md @@ -220,7 +220,7 @@ async def main(): client = ArtemisClient( "http://artemis-host:8000", device_serial="emulator-5554", # optional: target specific device serial - default_profile="flash", # "flash" (fast reactive) or "pro" (deep reasoning) + default_profile="flash", # "flash" (fast reactive) or "pro" (deep reasoning) ) result = await client.run( @@ -273,8 +273,8 @@ Evaluated on [AndroidWorld](https://github.com/google-research/android_world) ARTEMIS supports two execution profiles tailored for different automation requirements: -* **Flash Profile (`--profile flash`)**: Fast and token-efficient reactive loop (~3–5s per step). Ideal for routine, deterministic UI tasks within 25–30 steps. *Limitations*: Does not support long-term state monitoring, video stream analysis, or multi-step failure self-healing. -* **Pro Profile (`--profile pro`)**: Our most capable model architecture (~15–40s per step). Powered by a multi-agent graph with planning, visual verification, and automated recovery. Capable of handling 100+ step long-horizon workflows, continuous device state monitoring, and multimedia video analysis. +* **Flash Profile (`--profile flash`)**: Fast and token-efficient reactive loop (~3–5s per step): one model observes the live screen, thinks, and acts, with no graph orchestration. Ideal for routine, deterministic UI tasks. The loop is unbounded by default (`agent.flash.max_turns`, 0 = unlimited) because history is compressed rather than capped: Flash shares the Pro session transcript ledger (session-relative `T+mm:ss` clock, screenshots folded into visual summaries, older steps chunked into eras and recallable on demand via `recall_history`) and can query the session recording through `video_analyzer`. Transient UI (auto-fading control bars, toasts) is handled by chaining taps into one `click_sequence`. *Limitations*: No task plan or notes, no pre-execution safety net, no checkpoint verification or final report, and no ADB shell. +* **Pro Profile (`--profile pro`)**: Our most capable architecture (~15–40s per step), built as a multi-agent graph. A **Planner** maintains a living Markdown task plan with milestones and `verify` / `assert` check items; the **Operator** executes it with the full toolset (Explorer grounding whose `flash` / `pro` / `ultra` tier is a user setting per profile — `pro.explorer.mode` / `flash.explorer_mode` in `config/artemis.jsonc` or `--explorer-pro-mode` — never chosen by the agent; notes, history recall, video analysis, ADB diagnostics). Every single action passes a pre-execution **Safety Net** (XML-first, pixel fallback), while multi-action **fast-action bursts** fire back to back to beat turn latency on transient UI. A blocked or failed action opens an **execution incident** that stays in the Operator's context until a later action succeeds, so recovery is handled by the Operator itself with no separate repair agent. A read-only **Checker** verifies plan checkpoints and runs an exit final review against the original goal (`--verification-level`: `off` / `final` (default) / `checkpoints` / `strict`), and plan milestone edits get an advisory review. Handles 100+ step long-horizon workflows, `[Loop:continuous]` monitoring, and an optional written report. ## Roadmap diff --git a/README_CN.md b/README_CN.md index 5a13612..454ad45 100644 --- a/README_CN.md +++ b/README_CN.md @@ -221,7 +221,7 @@ async def main(): client = ArtemisClient( "http://artemis-host:8000", device_serial="emulator-5554", # 可选:指定目标设备序列号(不传则自动选择空闲设备) - default_profile="flash", # "flash" 极速校验 或 "pro" 深度推理自愈 + default_profile="flash", # "flash" 极速校验 或 "pro" 深度推理自愈 ) result = await client.run( @@ -275,8 +275,8 @@ if __name__ == "__main__": ARTEMIS 提供两种运行模式以适应不同的自动化需求: -* **Flash 模式 (`--profile flash`)**:轻量极速且省 Token 的响应式循环(单步约 3–5 秒),适合 25–30 步以内的常规确定性 UI 操作。*局限性*:不支持长时间状态监控、视频流分析以及复杂的多步自愈恢复。 -* **Pro 模式 (`--profile pro`)**:最强大的模型架构(单步约 15–40 秒),基于多智能体图编排,具备自主规划、视觉校验与闭环自愈能力,支持 100+ 步的长程复杂业务流、设备持续状态监控与多媒体视频分析。 +* **Flash 模式 (`--profile flash`)**:轻量极速且省 Token 的响应式循环(单步约 3–5 秒):单个模型观察实时屏幕、思考、执行,不经过图编排,适合常规确定性 UI 操作。默认不限步数(`agent.flash.max_turns`,0 表示不限),因为历史是被压缩而不是被截断:Flash 与 Pro 共用同一套会话记录账本(相对测试时间 `T+mm:ss`、截图折叠为视觉摘要、更早的步骤分块归档并可通过 `recall_history` 按需召回),并可调用 `video_analyzer` 分析整段会话录屏;对自动消失的控制栏、toast 等瞬态控件,用 `click_sequence` 把多次点击串成一个原子序列。*局限性*:没有任务计划与笔记、没有执行前安全网、没有检查点校验与最终报告、不能执行 ADB 命令。 +* **Pro 模式 (`--profile pro`)**:最强大的架构(单步约 15–40 秒),由多智能体图编排:**Planner** 维护一份带里程碑和 `verify` / `assert` 检查项的 Markdown 任务计划;**Operator** 按计划执行,拥有全套工具(Explorer 元素定位,其 `flash` / `pro` / `ultra` 三档感知深度是按运行模式设置的用户配置——`config/artemis.jsonc` 里的 `pro.explorer.mode` / `flash.explorer_mode` 或 `--explorer-pro-mode`——智能体自身不会选择档位;笔记、历史召回、视频分析、ADB 诊断)。单个动作执行前都经过 **Safety Net** 校验(XML 优先、像素兜底),而一轮里的多个动作则作为**快速动作序列 (fast-action burst)** 连发,赶在瞬态控件消失前完成操作。动作被拦截或失败时会打开一条**执行事故 (execution incident)**,持续留在 Operator 的上下文里直到后续动作执行成功,恢复由 Operator 自己完成,不再有独立的修复智能体。只读的 **Checker** 校验计划中的检查点,并在退出前对照原始目标做终审(`--verification-level`:`off` / `final`(默认)/ `checkpoints` / `strict`),计划里程碑的改动会得到建议式复核。支持 100+ 步的长程复杂业务流、`[Loop:continuous]` 持续监控以及可选的书面报告。 ## 路线图 diff --git a/apps/admin_console/core/state.py b/apps/admin_console/core/state.py index f88e9f0..cc2c4a3 100644 --- a/apps/admin_console/core/state.py +++ b/apps/admin_console/core/state.py @@ -236,6 +236,7 @@ class ServerState: state = ServerState() import sys + if __name__ == "admin_console.core.state": sys.modules["apps.admin_console.core.state"] = sys.modules[__name__] elif __name__ == "apps.admin_console.core.state": diff --git a/apps/admin_console/database/repositories/session_repository.py b/apps/admin_console/database/repositories/session_repository.py index 07c59cb..5fe61fb 100644 --- a/apps/admin_console/database/repositories/session_repository.py +++ b/apps/admin_console/database/repositories/session_repository.py @@ -13,6 +13,8 @@ # limitations under the License. import logging +import os +import sqlite3 import time from typing import Any @@ -74,10 +76,12 @@ class SessionRepository: ) if cursor.fetchone() is None: return {} - columns = { - str(row[1]) for row in cursor.execute("PRAGMA table_info(video_recordings)") - } - ready_clause = "status = 'ready'" if "status" in columns else "end_time IS NOT NULL" + columns = {str(row[1]) for row in cursor.execute("PRAGMA table_info(video_recordings)")} + # Failed recordings may still be recoverable from disk. + # Callers exclude in-progress sessions before resolving paths. + ready_clause = ( + "status IN ('ready', 'failed')" if "status" in columns else "end_time IS NOT NULL" + ) cursor.execute( "SELECT session_id, local_video_path FROM video_recordings " "WHERE session_id IS NOT NULL AND local_video_path IS NOT NULL " @@ -104,7 +108,9 @@ class SessionRepository: return None result = dict(row) if "status" not in result: - result["status"] = "ready" if result.get("end_time") is not None else "recording" + result["status"] = ( + "ready" if result.get("end_time") is not None else "recording" + ) result.setdefault("error", None) return result except Exception: @@ -147,6 +153,40 @@ class SessionRepository: except Exception: return {} + def get_unfinalized_video_recordings(self) -> list[dict[str, Any]]: + """Recordings whose worker never finalized them, oldest first. + + Rows of sessions that are still ``running`` are skipped: their worker + (possibly owned by another daemon) may still be writing the file. + """ + try: + with db_session(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT name FROM sqlite_master WHERE type='table' AND name='video_recordings'" + ) + if cursor.fetchone() is None: + return [] + columns = { + str(row[1]) for row in cursor.execute("PRAGMA table_info(video_recordings)") + } + pending_clause = ( + "v.status IN ('recording', 'finalizing', 'failed')" + if "status" in columns + else "v.end_time IS NULL" + ) + cursor.execute( + "SELECT v.session_id, v.local_video_path, v.start_time, " + "s.status AS session_status, s.start_time AS session_start_time " + "FROM video_recordings v LEFT JOIN sessions s ON s.session_id = v.session_id " + f"WHERE v.session_id IS NOT NULL AND v.local_video_path IS NOT NULL " + f"AND {pending_clause} ORDER BY v.start_time ASC" + ) + rows = [dict(r) for r in cursor.fetchall()] + except (OSError, sqlite3.Error): + return [] + return [row for row in rows if str(row.get("session_status") or "").lower() != "running"] + def mark_recording_failed_if_pending(self, session_id: str, error: str) -> bool: """Close a recording lifecycle when its worker exits before finalization.""" try: @@ -174,8 +214,16 @@ class SessionRepository: "WHERE session_id = ?", (str(local_video_path), time.time(), str(session_id)), ) + updated = cursor.rowcount > 0 + if os.path.exists(str(local_video_path)): + # Mirror the worker's own finalization so the session row + # and trace tooling see the recovered file too. + cursor.execute( + "UPDATE sessions SET video_filepath = ? WHERE session_id = ?", + (str(local_video_path), str(session_id)), + ) conn.commit() - return cursor.rowcount > 0 + return updated except Exception: return False @@ -324,8 +372,7 @@ class SessionRepository: # read_status itself never raises; this guards the # lock/write side of update_trace_status. logger.warning( - "Could not mark trace %s failed during orphan " - "reconciliation: %s", + "Could not mark trace %s failed during orphan reconciliation: %s", row["session_id"], exc, ) diff --git a/apps/admin_console/routers/media.py b/apps/admin_console/routers/media.py index c3fb253..ec9bd0c 100644 --- a/apps/admin_console/routers/media.py +++ b/apps/admin_console/routers/media.py @@ -149,7 +149,9 @@ def _get_session_video_sync(session_id: str): v_url = media_service.resolve_video_url(row_dict, video_rec_map, video_idx) video_segments = media_service.resolve_video_segments(v_url) if v_url: - version = int(float((recording or {}).get("end_time") or row_dict.get("end_time") or 0) * 1000) + version = int( + float((recording or {}).get("end_time") or row_dict.get("end_time") or 0) * 1000 + ) separator = "&" if "?" in v_url else "?" versioned_url = f"{v_url}{separator}v={version}" if version else v_url for segment in video_segments: @@ -188,3 +190,9 @@ async def get_task_plan(session_id: str): @router.get("/api/sessions/{session_id}/notes") async def get_all_notes(session_id: str): return {"notes": media_service.get_session_notes_content(session_id)} + + +@router.get("/api/sessions/{session_id}/checks") +async def get_session_checks(session_id: str): + """Checker verdict ledger + run outcome (backfill for the Checker panel).""" + return media_service.get_session_checks(session_id) diff --git a/apps/admin_console/routers/sessions.py b/apps/admin_console/routers/sessions.py index 14ccc06..d270e7f 100644 --- a/apps/admin_console/routers/sessions.py +++ b/apps/admin_console/routers/sessions.py @@ -64,11 +64,10 @@ def _list_sessions_sync(): try: from artemis.runtime import DeviceExecutionLock + active_owners = DeviceExecutionLock.get_active_owners() active_owner_sids = { - str(owner.session_id) - for owner in active_owners.values() - if owner.session_id + str(owner.session_id) for owner in active_owners.values() if owner.session_id } except Exception: active_owner_sids = set() diff --git a/apps/admin_console/routers/system.py b/apps/admin_console/routers/system.py index a2370ea..5127eca 100644 --- a/apps/admin_console/routers/system.py +++ b/apps/admin_console/routers/system.py @@ -297,7 +297,11 @@ async def get_credentials(): providers = ("google", "openai", "anthropic", "openrouter", "ocr") status = {name: bool(settings.get_api_key(name)) for name in providers} status["gemini"] = status["google"] - return {"providers": [{"name": name, "configured": configured} for name, configured in status.items()]} + return { + "providers": [ + {"name": name, "configured": configured} for name, configured in status.items() + ] + } @router.post("/credentials/test") @@ -370,7 +374,7 @@ async def update_credentials(request: UpdateCredentialsRequest): async def get_model_config_and_env(): """Retrieve the current active artemis.jsonc configuration and .env status for custom setup.""" import os - from artemis.config.paths import ROOT_DIR, get_config_path + from artemis.config.paths import get_config_path, get_env_file from artemis.config import settings from artemis.utils.file import load_jsonc @@ -390,7 +394,7 @@ async def get_model_config_and_env(): config_content = f"// Error reading config: {e}" # 2. Env file resolution - env_path = ROOT_DIR / ".env" + env_path = get_env_file() # 3. Relevant Env Variables status def get_real_env_value(key_name: str, provider: str) -> str | None: diff --git a/apps/admin_console/routers/tasks.py b/apps/admin_console/routers/tasks.py index d732958..38650d6 100644 --- a/apps/admin_console/routers/tasks.py +++ b/apps/admin_console/routers/tasks.py @@ -90,8 +90,7 @@ async def run_task(request: RunRequest): ( item for item in state.queue_items - if isinstance(item, dict) - and str(item.get("session_id")) == requested_sid + if isinstance(item, dict) and str(item.get("session_id")) == requested_sid ), None, ) @@ -122,9 +121,7 @@ async def run_task(request: RunRequest): # fail downstream with a clear error instead. if request.device_serial: try: - rejection = await device_pool.validate_explicit_serial_async( - request.device_serial - ) + rejection = await device_pool.validate_explicit_serial_async(request.device_serial) except Exception: rejection = None if rejection: @@ -144,19 +141,19 @@ async def run_task(request: RunRequest): # prefers the diagnostics target preference, then any unlocked ready # device); the verified serial is bound below. target_serial = request.device_serial - device_probe = await readiness_engine.run_device_submission_probe( - target_serial=target_serial - ) + device_probe = await readiness_engine.run_device_submission_probe(target_serial=target_serial) if device_probe and device_probe.summary in {"Device Locked", "Lock State Unknown"}: locked_serial = ( - device_probe.metadata.get("active_device", {}).get("serial") - or target_serial - or "" + device_probe.metadata.get("active_device", {}).get("serial") or target_serial or "" ) detail = ( - f"Android device {locked_serial} is locked. Unlock it and enter the home screen before running a task.".replace(" ", " ").strip() + f"Android device {locked_serial} is locked. Unlock it and enter the home screen before running a task.".replace( + " ", " " + ).strip() if device_probe.summary == "Device Locked" - else f"Android device {locked_serial} lock state could not be verified. Keep it unlocked on the home screen and try again.".replace(" ", " ").strip() + else f"Android device {locked_serial} lock state could not be verified. Keep it unlocked on the home screen and try again.".replace( + " ", " " + ).strip() ) raise HTTPException(status_code=409, detail=detail) @@ -173,6 +170,8 @@ async def run_task(request: RunRequest): profile=request.profile or "flash", expected_output=request.expected_output, enable_outputter=request.enable_outputter, + verification_level=request.verification_level, + explorer_mode=request.explorer_mode, locked_app_package=request.locked_app_package, app_path=request.app_path, device_serial=target_serial, @@ -182,6 +181,22 @@ async def run_task(request: RunRequest): ) +@router.get("/api/run/defaults") +async def get_run_defaults(): + """Effective Pro-profile tuning defaults from the agent config. + + The launcher's sliders start here so they reflect ``artemis.jsonc`` (and the + ``ARTEMIS_EXPLORER_VERSION`` override) instead of a hard-coded guess. + """ + from artemis.config import load_agent_config, verification_level_for_checker + + agent_cfg = load_agent_config() + return { + "verification_level": verification_level_for_checker(agent_cfg.checker), + "explorer_mode": agent_cfg.explorer.resolve(profile="pro"), + } + + @router.get("/api/devices") async def list_devices(): """List all connected Android devices with their busy / idle status.""" @@ -223,7 +238,6 @@ async def stop_task( return {"status": "no_running_task"} - @router.post("/api/resume") async def resume_task(): resumed = task_queue_service.resume_task() @@ -269,12 +283,14 @@ async def get_status(): (global_owner.session_id if global_owner else None) or state.active_session_id or (running_task.get("session_id") if running_task else None) - or (active_tasks[0]["session_id"] if active_tasks and active_tasks[0].get("session_id") else None) + or ( + active_tasks[0]["session_id"] + if active_tasks and active_tasks[0].get("session_id") + else None + ) or (session_repo.get_running_session_id() if is_running else None) ) - owner_connection = ( - state.active_connections.get(str(running_sid), {}) if running_sid else {} - ) + owner_connection = state.active_connections.get(str(running_sid), {}) if running_sid else {} running_goal = ( owner_connection.get("goal") or (global_owner.description if global_owner else None) @@ -283,8 +299,10 @@ async def get_status(): or (active_tasks[0]["goal"] if active_tasks else None) ) - active_profile = owner_connection.get("profile") or state.current_profile or ( - running_task.get("profile") if running_task and not global_owner else None + active_profile = ( + owner_connection.get("profile") + or state.current_profile + or (running_task.get("profile") if running_task and not global_owner else None) ) if not active_profile and (running_sid or latest_session_id): check_sid = running_sid or latest_session_id @@ -407,7 +425,11 @@ async def stream_events(session_id: str = "active", client: str | None = None): active_sid = state.active_session_id if not active_sid: running_item = next( - (t for t in state.queue_items if isinstance(t, dict) and t.get("status") == "running"), + ( + t + for t in state.queue_items + if isinstance(t, dict) and t.get("status") == "running" + ), None, ) if running_item: @@ -435,16 +457,14 @@ async def stream_events(session_id: str = "active", client: str | None = None): recorded_steps = step_repo.get_session_steps(str(active_sid)) for step_dict in recorded_steps: yield ( - "event: step_recorded\n" - f"data: {json.dumps(step_dict, default=str)}\n\n" + f"event: step_recorded\ndata: {json.dumps(step_dict, default=str)}\n\n" ) except Exception as exc: print(f"[Stream] Could not replay active steps: {exc}") else: for progress_event in state.get_startup_progress(session_id): yield ( - "event: startup_progress\n" - f"data: {json.dumps(progress_event, default=str)}\n\n" + f"event: startup_progress\ndata: {json.dumps(progress_event, default=str)}\n\n" ) shutdown_waiter = asyncio.create_task(state.shutdown_event.wait()) diff --git a/apps/admin_console/schemas/task_schema.py b/apps/admin_console/schemas/task_schema.py index ad89cb9..144a972 100644 --- a/apps/admin_console/schemas/task_schema.py +++ b/apps/admin_console/schemas/task_schema.py @@ -21,6 +21,11 @@ class RunRequest(BaseModel): profile: str | None = "flash" expected_output: str | None = None enable_outputter: bool | None = None + # Pro-profile tuning (ignored by the Flash profile): a coarse Checker preset + # ('off' | 'final' | 'checkpoints' | 'strict') and the Explorer perception + # version used by the Operator ('flash' | 'pro' | 'ultra'). + verification_level: str | None = None + explorer_mode: str | None = None locked_app_package: str | None = None app_path: str | None = None device_serial: str | None = None @@ -29,7 +34,6 @@ class RunRequest(BaseModel): conversation_id: str | None = None - class ReplayRequest(BaseModel): device_id: str user_submits: dict @@ -41,4 +45,3 @@ class StopRequest(BaseModel): session_id: str | None = None device_id: str | None = None all: bool = False - diff --git a/apps/admin_console/server.py b/apps/admin_console/server.py index 8498bd1..f6f12a2 100644 --- a/apps/admin_console/server.py +++ b/apps/admin_console/server.py @@ -67,6 +67,7 @@ from artemis.config import ( WORKSPACE_ROOT, init_ls_address, ) +from artemis.resources import get_bundled_showcase_dist try: from admin_console.core.security import SameOriginBoundaryMiddleware @@ -141,6 +142,9 @@ async def on_startup(): cleaned = session_repo.cleanup_orphans_on_startup() if cleaned > 0: print(f"[ServerStartup] Marked {cleaned} orphaned running session(s) as failed.") + # Workers killed together with a previous daemon never remuxed their + # recordings; publish whatever raw files they left behind. + asyncio.create_task(asyncio.to_thread(task_queue_service.recover_orphaned_recordings_on_launch)) await ipc_service.start_server() state.worker_task = asyncio.create_task(task_queue_service.queue_worker()) @@ -174,9 +178,7 @@ async def on_shutdown(): run_task.cancel() if run_tasks: try: - await asyncio.wait_for( - asyncio.gather(*run_tasks, return_exceptions=True), timeout=5.0 - ) + await asyncio.wait_for(asyncio.gather(*run_tasks, return_exceptions=True), timeout=5.0) except (TimeoutError, asyncio.CancelledError): pass for run in list(state.active_runs.values()): @@ -231,7 +233,7 @@ except ImportError: # Showcase UI (Angular 19) Unified Single-Port SPA Static Hosting # ------------------------------------------------------------------------------ def _get_showcase_dist() -> Path: - """Return the Showcase UI Angular build directory.""" + """Return the source build, or the immutable build bundled in a wheel.""" base_dist = _workspace_root / "apps" / "showcase_ui" / "dist" candidates = [ base_dist / "frontend" / "browser", @@ -242,6 +244,9 @@ def _get_showcase_dist() -> Path: for candidate in candidates: if candidate.is_dir() and (candidate / "index.html").exists(): return candidate + bundled_dist = get_bundled_showcase_dist() + if bundled_dist is not None: + return bundled_dist return base_dist / "frontend" / "browser" diff --git a/apps/admin_console/services/media_service.py b/apps/admin_console/services/media_service.py index e5ef8c6..adff66c 100644 --- a/apps/admin_console/services/media_service.py +++ b/apps/admin_console/services/media_service.py @@ -13,6 +13,7 @@ # limitations under the License. import base64 +from datetime import datetime import hashlib import json import logging @@ -20,6 +21,7 @@ import os from pathlib import Path import subprocess import threading +import time from typing import Any import urllib.parse from fastapi import HTTPException @@ -51,13 +53,23 @@ class MediaService: if suffix == ".mp4": return p if suffix in (".mkv", ".webm"): + if cls.is_live_recording(p): + # scrcpy is still appending to this file: converting now + # would freeze a few-seconds partial as recording.mp4. + return p src_stat = p.stat() cache_key = str(p) cached = cls._playable_cache.get(cache_key) if cached and cached[0] == src_stat.st_mtime and cached[1] == src_stat.st_size: return Path(cached[2]) mp4_cand = p.with_suffix(".mp4") - if mp4_cand.exists() and mp4_cand.stat().st_size > 0: + if ( + mp4_cand.exists() + and mp4_cand.stat().st_size > 0 + and mp4_cand.stat().st_mtime >= src_stat.st_mtime + ): + # An MP4 older than its source is a stale partial left by a + # mid-run scan; fall through and convert again. cls._playable_cache[cache_key] = ( src_stat.st_mtime, src_stat.st_size, @@ -88,6 +100,7 @@ class MediaService: @staticmethod def _convert_to_mp4(p: Path, mp4_cand: Path) -> Path: from artemis.utils.video import get_ffmpeg_path + ffmpeg = get_ffmpeg_path() # 1. Attempt fast copy remux subprocess.run( @@ -135,6 +148,115 @@ class MediaService: return mp4_cand return p + LIVE_RECORDING_GRACE_SECONDS = 10.0 + TRACE_STATUS_SUFFIXES = ("_PASS_", "_FAIL_", "_TESTFAIL_") + + @classmethod + def is_live_recording(cls, p: Path) -> bool: + """Whether ``p`` is a raw .mkv that scrcpy is still appending to. + + scrcpy touches the file continuously while recording; a raw file that + has not changed for a few seconds belongs to a finished (or killed) + recording and is safe to convert. + """ + if p.suffix.lower() != ".mkv": + return False + try: + return (time.time() - p.stat().st_mtime) < cls.LIVE_RECORDING_GRACE_SECONDS + except OSError: + return False + + @staticmethod + def is_browser_playable(p: Path) -> bool: + return p.suffix.lower() in (".mp4", ".webm") + + @classmethod + def strip_trace_status_suffix(cls, folder_name: str) -> str: + for marker in cls.TRACE_STATUS_SUFFIXES: + folder_name = folder_name.split(marker)[0] + return folder_name + + @classmethod + def _finalize_trace_folder_name(cls, folder: Path, session_start_time: float | None) -> Path: + """Give an unfinished temp trace folder the terminal name the worker uses.""" + try: + if folder.resolve().parent != Path(TRACES_PATH).resolve(): + return folder + except OSError: + return folder + if any(marker in folder.name for marker in cls.TRACE_STATUS_SUFFIXES): + return folder + try: + stamp = datetime.fromtimestamp(float(session_start_time or time.time())).strftime( + "%Y-%m-%dT%H-%M-%S" + ) + except (TypeError, ValueError, OSError, OverflowError): + stamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S") + target = folder.with_name(f"{folder.name}_FAIL_{stamp}") + if target.exists(): + return folder + try: + folder.rename(target) + except OSError as exc: + logger.warning("Could not rename orphaned trace folder %s: %s", folder, exc) + return folder + return target + + @classmethod + def recover_orphaned_recording( + cls, + local_video_path: str | Path | None, + session_start_time: float | None = None, + ) -> Path | None: + """Finalize the recording of a worker that died before its own stop path ran. + + A hard-killed worker leaves the raw scrcpy ``recording.mkv`` behind + (playable, but never remuxed) plus, possibly, a few-seconds partial + ``recording.mp4`` that a session-list scan converted mid-run. The raw + file is remuxed over that partial and removed on success, and the temp + trace folder is renamed the way the worker would have done it. + + Returns the final browser-playable path, or None when nothing usable + exists (the caller then records a terminal recording failure). + """ + if not local_video_path: + return None + source = Path(str(local_video_path)) + folder = source.parent + if not folder.is_dir(): + return None + raw = source if source.suffix.lower() == ".mkv" else folder / "recording.mkv" + mp4 = folder / "recording.mp4" + + def _non_empty(path: Path) -> bool: + try: + return path.is_file() and path.stat().st_size > 0 + except OSError: + return False + + if _non_empty(raw): + if cls.is_live_recording(raw): + return None + with cls._playable_cache_lock: + converted = cls._convert_to_mp4(raw, mp4) + cls._playable_cache.pop(str(raw), None) + if converted != mp4 or not _non_empty(mp4): + return None + try: + raw.unlink() + except OSError: + pass + final = mp4 + elif _non_empty(mp4): + final = mp4 + elif _non_empty(source) and cls.is_browser_playable(source): + final = source + else: + return None + + folder = cls._finalize_trace_folder_name(folder, session_start_time) + return folder / final.name + @staticmethod def path_to_video_url(p: Path) -> str: resolved = p.resolve() @@ -168,13 +290,18 @@ class MediaService: vfile = item / f"recording{ext}" if vfile.exists(): vfile = cls.ensure_browser_playable_video(vfile) + if not cls.is_browser_playable(vfile): + # Live (still recording) or unconvertible raw file. + break url = cls.path_to_video_url(vfile) idx[item.name] = url - prefix = item.name.split("_PASS_")[0].split("_FAIL_")[0] + prefix = cls.strip_trace_status_suffix(item.name) idx[prefix] = url break elif item.suffix in [".mp4", ".mkv", ".webm"]: item = cls.ensure_browser_playable_video(item) + if not cls.is_browser_playable(item): + continue url = cls.path_to_video_url(item) idx[item.stem] = url idx[item.name] = url @@ -199,15 +326,17 @@ class MediaService: v_fp = row_dict.get("video_filepath") if v_fp and os.path.exists(v_fp): p = cls.ensure_browser_playable_video(Path(v_fp)) - v_url = cls.path_to_video_url(p) + if cls.is_browser_playable(p): + v_url = cls.path_to_video_url(p) orig_rec = video_rec_map.get(s_id) if not v_url and orig_rec: p = Path(orig_rec) if p.exists(): p = cls.ensure_browser_playable_video(p) - v_url = cls.path_to_video_url(p) - else: + if cls.is_browser_playable(p): + v_url = cls.path_to_video_url(p) + if not v_url: folder_name = p.parent.name v_url = video_idx.get(folder_name) or video_idx.get(folder_name.split("_")[0]) @@ -221,13 +350,16 @@ class MediaService: vfile = sess_dir / f"recording{ext}" if vfile.exists(): vfile = cls.ensure_browser_playable_video(vfile) - v_url = cls.path_to_video_url(vfile) + if cls.is_browser_playable(vfile): + v_url = cls.path_to_video_url(vfile) break if not v_url: try: for item in sess_dir.iterdir(): if item.is_file() and item.suffix.lower() in [".mp4", ".webm", ".mkv"]: item = cls.ensure_browser_playable_video(item) + if not cls.is_browser_playable(item): + continue v_url = cls.path_to_video_url(item) break except OSError: @@ -260,7 +392,10 @@ class MediaService: segments = [] for item in payload.get("segments", []): segment_path = (manifest_path.parent / str(item["file"])).resolve() - if segment_path.parent != manifest_path.parent.resolve() or not segment_path.is_file(): + if ( + segment_path.parent != manifest_path.parent.resolve() + or not segment_path.is_file() + ): continue segments.append( { @@ -400,6 +535,43 @@ class MediaService: return f"Error reading task plan: {e}" return "No task plan created yet." + @staticmethod + def get_session_checks(session_id: str) -> dict[str, Any]: + """Checker material of one session for UI backfill: the append-only + verdict ledger (``check_ledger.jsonl``) and the machine-readable run + outcome (``run_outcome.json``), both written by + ``artemis.graph.checkpoints``. Missing files mean "no checks ran". + """ + session_dir = TRACES_PATH / session_id + records: list[dict[str, Any]] = [] + ledger_path = session_dir / "check_ledger.jsonl" + if ledger_path.is_file(): + try: + for line in ledger_path.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line: + continue + try: + rec = json.loads(line) + except ValueError: + continue + if isinstance(rec, dict): + records.append(rec) + except (OSError, ValueError) as e: + logger.warning(f"Failed to read check ledger for {session_id}: {e}") + + run_outcome: dict[str, Any] | None = None + outcome_path = session_dir / "run_outcome.json" + if outcome_path.is_file(): + try: + loaded = json.loads(outcome_path.read_text(encoding="utf-8")) + if isinstance(loaded, dict): + run_outcome = loaded + except (OSError, ValueError) as e: + logger.warning(f"Failed to read run outcome for {session_id}: {e}") + + return {"records": records, "run_outcome": run_outcome} + @staticmethod def get_session_notes_content(session_id: str) -> dict[str, str]: notes_dir = TRACES_PATH / session_id / "notes" diff --git a/apps/admin_console/services/task_queue_service.py b/apps/admin_console/services/task_queue_service.py index b16f726..b107bf6 100644 --- a/apps/admin_console/services/task_queue_service.py +++ b/apps/admin_console/services/task_queue_service.py @@ -16,8 +16,10 @@ import asyncio from datetime import datetime import logging import os +from pathlib import Path import shutil import sys +import threading import time from typing import Any import uuid @@ -43,8 +45,11 @@ from artemis.runtime import ( AdbEndpoint, AdbTarget, DeviceExecutionLock, + clear_cancel_request, current_adb_endpoint, + pid_is_alive, process_supervisor, + request_cancel, trace_store, ) @@ -59,6 +64,156 @@ class TaskQueueService: # Strong references to in-flight _execute_task_item tasks (asyncio itself only # keeps weak references to running tasks). _run_tasks: set[asyncio.Task] = set() + # Deadline enforcers for graceful stops (see _stop_worker_gracefully). + _forced_stop_tasks: set[asyncio.Task] = set() + + DEFAULT_CANCEL_GRACE_SECONDS = 45.0 + + @classmethod + def _cancel_grace_seconds(cls) -> float: + """How long a worker may finalize itself before it is killed. + + ``ARTEMIS_CANCEL_GRACE_SECONDS=0`` restores the legacy immediate kill. + """ + raw = os.getenv("ARTEMIS_CANCEL_GRACE_SECONDS") + if raw is None or not raw.strip(): + return cls.DEFAULT_CANCEL_GRACE_SECONDS + try: + return max(0.0, float(raw)) + except ValueError: + return cls.DEFAULT_CANCEL_GRACE_SECONDS + + @staticmethod + def _hard_kill(pid: int, process_created_at: float = 0.0) -> bool: + if not pid: + return False + if process_created_at and process_created_at > 0: + return process_supervisor.terminate_tree_verified(pid, process_created_at) + try: + return process_supervisor.terminate_tree(pid) + except Exception: + return False + + @classmethod + def _stop_worker_gracefully( + cls, + pid: Any, + process_created_at: float = 0.0, + session_id: str | None = None, + reason: str = "Task stopped from the Artemis frontend.", + ) -> tuple[bool, bool]: + """Ask a worker to cancel itself; hard-kill it once the grace period lapses. + + Workers are isolated from the daemon's console (and may belong to + another ingress process), so instead of a signal the daemon drops a + cancel marker the worker polls for. Honouring it runs the worker's + normal cancellation path: the screen recording is stopped and remuxed, + the trace folder is compiled, and the device lease is released. A + worker that never picks the marker up is killed after the grace period. + + Returns ``(stopped, deferred)``: ``stopped`` mirrors the legacy kill + result, ``deferred`` is True when the kill was handed to the deadline + enforcer instead of happening now. + """ + try: + pid_int = int(pid) if pid else 0 + except (TypeError, ValueError): + pid_int = 0 + grace = cls._cancel_grace_seconds() + if not pid_int or grace <= 0: + return cls._hard_kill(pid_int, process_created_at), False + + created_at = float(process_created_at or 0.0) + if created_at <= 0: + # PID markers carry the creation time so a leftover marker can never + # cancel a future process that reuses this PID. + try: + import psutil + + created_at = float(psutil.Process(pid_int).create_time()) + except Exception: + created_at = 0.0 + if not pid_is_alive(pid_int, created_at or None): + return True, False + + written = request_cancel( + session_id=str(session_id) if session_id else None, + pid=pid_int, + process_created_at=created_at, + reason=reason, + ) + if not written: + return cls._hard_kill(pid_int, created_at), False + + print( + f"[stop_tasks] Cancel requested for worker {pid_int}" + f" (session {session_id or 'n/a'}); forcing termination after {grace:.0f}s" + ) + cls._schedule_forced_stop(pid_int, created_at, grace, session_id) + return True, True + + @classmethod + def _stop_proc_gracefully(cls, proc: Any, session_id: Any) -> bool: + """Graceful variant for a locally spawned process; True when deferred.""" + pid = getattr(proc, "pid", None) + if not pid or cls._cancel_grace_seconds() <= 0: + return False + try: + _stopped, deferred = cls._stop_worker_gracefully( + pid, session_id=str(session_id) if session_id else None + ) + except Exception: + return False + return deferred + + @classmethod + def _schedule_forced_stop( + cls, pid: int, process_created_at: float, grace: float, session_id: Any + ) -> None: + """Kill ``pid`` if it is still alive once ``grace`` seconds have passed.""" + + def _still_alive() -> bool: + return pid_is_alive(pid, process_created_at or None) + + def _force() -> None: + print( + f"[stop_tasks] Worker {pid} (session {session_id or 'n/a'}) did not exit" + f" within {grace:.0f}s of the cancel request; forcing termination." + ) + cls._hard_kill(pid, process_created_at) + + async def _enforce_async() -> None: + deadline = time.monotonic() + grace + while time.monotonic() < deadline: + if not _still_alive(): + break + await asyncio.sleep(0.5) + else: + _force() + clear_cancel_request(session_id=str(session_id) if session_id else None, pid=pid) + + def _enforce_sync() -> None: + deadline = time.monotonic() + grace + while time.monotonic() < deadline: + if not _still_alive(): + break + time.sleep(0.5) + else: + _force() + clear_cancel_request(session_id=str(session_id) if session_id else None, pid=pid) + + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = None + if loop is not None: + task = loop.create_task(_enforce_async()) + cls._forced_stop_tasks.add(task) + task.add_done_callback(cls._forced_stop_tasks.discard) + return + threading.Thread( + target=_enforce_sync, name=f"artemis-forced-stop-{pid}", daemon=True + ).start() @staticmethod def _task_target(task_item: dict[str, Any]) -> AdbTarget: @@ -88,9 +243,7 @@ class TaskQueueService: ) @classmethod - def _broadcast_startup_progress( - cls, session_id: str | None, stage: str, message: str - ) -> None: + def _broadcast_startup_progress(cls, session_id: str | None, stage: str, message: str) -> None: if not session_id: return data = { @@ -223,8 +376,7 @@ class TaskQueueService: inside each worker process. """ print( - "[QueueWorker] Dispatcher initialized " - f"(concurrency mode: {cls._concurrency_mode()})." + f"[QueueWorker] Dispatcher initialized (concurrency mode: {cls._concurrency_mode()})." ) try: DeviceExecutionLock.cleanup_stale_locks() @@ -262,9 +414,7 @@ class TaskQueueService: # "running" state, so admission must count those too -- active_runs alone # lags behind by the subprocess startup latency. in_flight = [ - i - for i in state.queue_items - if isinstance(i, dict) and i.get("status") == "running" + i for i in state.queue_items if isinstance(i, dict) and i.get("status") == "running" ] busy_devices = state.busy_device_ids | { cls._task_target(i).lock_key for i in in_flight if i.get("device_serial") @@ -325,9 +475,7 @@ class TaskQueueService: state.current_profile = profile state.active_session_id = sess_id - cls._broadcast_startup_progress( - sess_id, "launching", "Starting the execution process" - ) + cls._broadcast_startup_progress(sess_id, "launching", "Starting the execution process") # Broadcast session_started so all connected clients know the task has started cls._broadcast_event( @@ -353,6 +501,8 @@ class TaskQueueService: """Assemble the worker subprocess command line and environment.""" expected_output = task_item.get("expected_output") enable_outputter = task_item.get("enable_outputter") + verification_level = task_item.get("verification_level") + explorer_mode = task_item.get("explorer_mode") locked_app = task_item.get("locked_app_package") or task_item.get("locked_app") app_path = task_item.get("app_path") @@ -395,6 +545,10 @@ class TaskQueueService: cmd.extend(["--output-description", str(expected_output)]) if enable_outputter is not None: cmd.append("--enable-outputter" if enable_outputter else "--disable-outputter") + if verification_level: + cmd.extend(["--verification-level", str(verification_level)]) + if explorer_mode: + cmd.extend(["--explorer-pro-mode", str(explorer_mode)]) if locked_app: cmd.extend(["--locked-app", str(locked_app)]) if app_path: @@ -439,9 +593,7 @@ class TaskQueueService: print( f"[QueueWorker] Could not record worker pid in status.json for {sess_id}: {exc}" ) - cls._broadcast_startup_progress( - sess_id, "process_ready", "Execution process started" - ) + cls._broadcast_startup_progress(sess_id, "process_ready", "Execution process started") ingress_type = str(task_item.get("ingress", "frontend")) DeviceExecutionLock.transfer_reservation( str(task_item.get("queue_ticket")), @@ -467,9 +619,7 @@ class TaskQueueService: except Exception: log_path = None if isinstance(proc.stdout, asyncio.StreamReader): - return asyncio.create_task( - cls._forward_worker_output(proc.stdout, log_path) - ) + return asyncio.create_task(cls._forward_worker_output(proc.stdout, log_path)) return None @classmethod @@ -481,9 +631,7 @@ class TaskQueueService: str(sess_id) in getattr(state, "cancelled_session_ids", set()) or run_key in state.manually_stopped_run_ids ): - print( - f"[QueueWorker] Task [{sess_id}] was cancelled during launch. Terminating." - ) + print(f"[QueueWorker] Task [{sess_id}] was cancelled during launch. Terminating.") await cls._terminate_worker_process(proc) @classmethod @@ -505,23 +653,21 @@ class TaskQueueService: # against when status.json is stale, so a failed DB # write must not pass silently. logger.error( - "[QueueWorker] Could not persist terminal DB status " - "'%s' for session %s", + "[QueueWorker] Could not persist terminal DB status '%s' for session %s", new_status, sess_id, ) else: - print( - f"[QueueWorker] Preserved authoritative session {sess_id} " - f"status '{new_status}'" - ) + print(f"[QueueWorker] Preserved authoritative session {sess_id} status '{new_status}'") # A stale status.json would leave MCP pollers seeing "running" # until their next DB reconcile, so retry transient write # failures before giving up. for attempt in range(3): try: if trace_store.read_status(str(sess_id)): - canonical_mcp_status = "completed" if new_status in ("completed", "success") else new_status + canonical_mcp_status = ( + "completed" if new_status in ("completed", "success") else new_status + ) trace_store.update_trace_status( str(sess_id), canonical_mcp_status, @@ -542,37 +688,68 @@ class TaskQueueService: @classmethod async def _recover_or_fail_recording(cls, sess_id: Any) -> None: - """Attempt recovery of a not-yet-ready recording, else mark it failed.""" + """Finalize a recording whose worker died before it could, else mark it failed. + + A worker that stops gracefully finalizes its own recording and this is + a no-op. A hard-killed (or crashed) worker leaves the raw scrcpy file it + registered at recording start; remuxing that file is all that is + needed to publish the full video. + """ rec_info = session_repo.get_video_recording_for_session(sess_id) rec_status = (rec_info or {}).get("status") - recovered_video_path = None - if rec_status != "ready": + if rec_status == "ready": + return + + recovered_url = None + # 1. Direct recovery from the recording row written at recording start. + try: + local_video_path = (rec_info or {}).get("local_video_path") + if local_video_path: + start_time = (rec_info or {}).get("start_time") + if not start_time: + session_row = session_repo.get_session_by_id(sess_id) + start_time = dict(session_row).get("start_time") if session_row else None + final_path = await asyncio.to_thread( + media_service.recover_orphaned_recording, + local_video_path, + start_time, + ) + if final_path: + session_repo.mark_recording_ready(sess_id, str(final_path)) + recovered_url = media_service.path_to_video_url(Path(final_path)) + except Exception as rec_err: + print(f"[QueueWorker] Error finalizing orphaned recording: {rec_err}") + + # 2. Fallback: locate an already finalized file by folder / session naming. + if not recovered_url: try: video_rec_map = session_repo.get_video_recordings_map() video_idx = await asyncio.to_thread(media_service.build_video_index) - recovered_url = await asyncio.to_thread( + fallback_url = await asyncio.to_thread( media_service.resolve_video_url, {"session_id": sess_id}, video_rec_map, video_idx, ) - if recovered_url: - session_repo.mark_recording_ready(sess_id, recovered_url) - cls._broadcast_event( - "recording_ready", - {"session_id": sess_id, "video_url": recovered_url}, - ) - recovered_video_path = recovered_url + if fallback_url: + session_repo.mark_recording_ready(sess_id, fallback_url) + recovered_url = fallback_url except Exception as rec_err: print(f"[QueueWorker] Error attempting recording recovery: {rec_err}") - if not recovered_video_path and rec_status != "ready": - recording_error = "Task worker exited before recording finalization completed" - if session_repo.mark_recording_failed_if_pending(sess_id, recording_error): - cls._broadcast_event( - "recording_failed", - {"session_id": sess_id, "error": recording_error}, - ) + if recovered_url: + cls._broadcast_event( + "recording_ready", + {"session_id": sess_id, "video_url": recovered_url}, + ) + return + + recording_error = "Task worker exited before recording finalization completed" + if session_repo.mark_recording_failed_if_pending(sess_id, recording_error): + cls._broadcast_event( + "recording_failed", + {"session_id": sess_id, "error": recording_error}, + ) @classmethod def _announce_session_end( @@ -624,6 +801,15 @@ class TaskQueueService: state.cancelled_session_ids.discard(str(sess_id)) state.cancelled_session_ids.discard(run_key) state.manually_stopped_run_ids.discard(run_key) + try: + clear_cancel_request( + session_id=str(sess_id) if sess_id else None, + pid=getattr(proc, "pid", None), + ) + except (OSError, TypeError, ValueError): + # Marker cleanup is best-effort: an unwritable temp dir or an odd + # pid value must not block releasing the run slot. + pass state.active_runs.pop(run_key, None) if proc is not None and state.current_process is proc: state.current_process = None @@ -665,9 +851,7 @@ class TaskQueueService: env=env, **cls._subprocess_creation_kwargs(), ) - cls._register_worker_run( - task_item, run_key, sess_id, goal, profile, target, proc - ) + cls._register_worker_run(task_item, run_key, sess_id, goal, profile, target, proc) output_task = cls._start_output_forwarder(sess_id, proc) await cls._terminate_if_cancelled_during_launch(run_key, sess_id, proc) @@ -685,9 +869,7 @@ class TaskQueueService: sess_id, returncode, manual_stop ) await cls._recover_or_fail_recording(sess_id) - cls._announce_session_end( - task_item, sess_id, goal, new_status, manual_stop - ) + cls._announce_session_end(task_item, sess_id, goal, new_status, manual_stop) except asyncio.CancelledError: print(f"[QueueWorker] Task [{sess_id}] received cancellation signal.") @@ -763,9 +945,7 @@ class TaskQueueService: return None @classmethod - async def _reject_unavailable_device( - cls, device_serial: str | None - ) -> dict[str, Any] | None: + async def _reject_unavailable_device(cls, device_serial: str | None) -> dict[str, Any] | None: """Return the rejection response for an unattached explicit serial, if any.""" # Strict device binding: reject an explicitly requested serial that is not # attached and authorized, instead of silently running on another device. @@ -804,6 +984,8 @@ class TaskQueueService: device_serial: str | None, ingress: str, conversation_id: str | None, + verification_level: str | None = None, + explorer_mode: str | None = None, ) -> dict[str, Any]: """Reserve a device slot and build one pending queue item for a goal.""" sess_id = single_session_id if single_session_id else str(uuid.uuid4()) @@ -829,6 +1011,8 @@ class TaskQueueService: "profile": profile or "flash", "expected_output": expected_output, "enable_outputter": enable_outputter, + "verification_level": verification_level, + "explorer_mode": explorer_mode, "locked_app_package": locked_app_package, "app_path": app_path, "device_serial": assigned_serial, @@ -854,8 +1038,19 @@ class TaskQueueService: ingress: str = "frontend", session_id: str | None = None, conversation_id: str | None = None, + verification_level: str | None = None, + explorer_mode: str | None = None, ) -> dict[str, Any]: - """Enqueues one or more goals and wakes up the background worker.""" + """Enqueues one or more goals and wakes up the background worker. + + ``verification_level`` and ``explorer_mode`` are Pro-profile tuning knobs + forwarded to the worker as ``--verification-level`` / ``--explorer-pro-mode``; + they are normalised here so the queue item and the CLI see one spelling. + """ + verification_level = ( + str(verification_level).strip().lower() or None if verification_level else None + ) + explorer_mode = str(explorer_mode).strip().lower() or None if explorer_mode else None cls.ensure_worker_running() enqueued_tasks = [] @@ -888,6 +1083,8 @@ class TaskQueueService: device_serial, ingress, conversation_id, + verification_level=verification_level, + explorer_mode=explorer_mode, ) state.queue_items.append(task_item) enqueued_tasks.append(task_item) @@ -931,9 +1128,10 @@ class TaskQueueService: for dev_owner in list(active_owners.values()): if dev_owner and DeviceExecutionLock.is_active_owner(dev_owner): - process_supervisor.terminate_tree_verified( + cls._stop_worker_gracefully( dev_owner.pid, dev_owner.process_created_at, + session_id=dev_owner.session_id, ) DeviceExecutionLock.cleanup_stale_locks(dev_owner.device_id) sid = dev_owner.session_id @@ -973,20 +1171,22 @@ class TaskQueueService: state.cancelled_session_ids.add(str(run_key)) run_proc = run.get("process") if run_proc is not None and run_proc.returncode is None: - try: - run_proc.kill() - except (ProcessLookupError, OSError): - # Process already exited between the check and the kill. - pass + if not cls._stop_proc_gracefully(run_proc, run.get("session_id") or run_key): + try: + run_proc.kill() + except (ProcessLookupError, OSError): + # Process already exited between the check and the kill. + pass # active_runs entries are popped by each run's finalizer once the # process exit is observed; clearing them here would free the device # slots before the processes are actually gone. if state.current_process: - try: - state.current_process.kill() - except (ProcessLookupError, OSError): - # Process already exited between the check and the kill. - pass + if not cls._stop_proc_gracefully(state.current_process, state.active_session_id): + try: + state.current_process.kill() + except (ProcessLookupError, OSError): + # Process already exited between the check and the kill. + pass state.current_process = None @classmethod @@ -1167,19 +1367,24 @@ class TaskQueueService: stopped = False reservation_cancelled = False if owner and DeviceExecutionLock.is_active_owner(owner): - stopped = process_supervisor.terminate_tree_verified( + stopped, _deferred = cls._stop_worker_gracefully( owner.pid, owner.process_created_at, + session_id=owner.session_id or target_sid, ) DeviceExecutionLock.cleanup_stale_locks(owner.device_id) elif owner is None and owner_record_exists and not target_sid: # Never fall back to a frontend PID while another process has an # owner record that is still being published or cannot be parsed. return None - elif owner is None and local_proc is not None and ( - local_run is not None - or not target_sid - or str(state.active_session_id) == target_sid + elif ( + owner is None + and local_proc is not None + and ( + local_run is not None + or not target_sid + or str(state.active_session_id) == target_sid + ) ): # The locally-managed worker can be stopped during its short # initialization window before Agent acquires the device lease. @@ -1187,21 +1392,28 @@ class TaskQueueService: state.cancelled_session_ids.add(target_sid) elif local_run_key is not None: state.manually_stopped_run_ids.add(str(local_run_key)) + deferred = False if local_pid: try: - stopped = process_supervisor.terminate_tree(local_pid) + stopped, deferred = cls._stop_worker_gracefully( + local_pid, + session_id=target_sid or (local_run or {}).get("session_id"), + ) except Exception: stopped = False - try: - local_proc.kill() - stopped = True - except (ProcessLookupError, OSError): - # Process already exited; terminate_tree above may have got it. - pass + if not deferred: + try: + local_proc.kill() + stopped = True + except (ProcessLookupError, OSError): + # Process already exited; the stop above may have got it. + pass stopped = True is_local_owner = True - elif owner is None and local_item and ( - not target_sid or str(local_item.get("session_id")) == target_sid + elif ( + owner is None + and local_item + and (not target_sid or str(local_item.get("session_id")) == target_sid) ): # Cancel a frontend submission before its worker has started. This # does not touch pending reservations created by other ingresses. @@ -1226,12 +1438,10 @@ class TaskQueueService: row_pid = row.get("pid") if row_pid and session_repo.process_is_alive(row_pid): try: - process_supervisor.terminate_tree(int(row_pid)) + cls._stop_worker_gracefully(int(row_pid), session_id=target_sid) except Exception as exc: # Report it: a surviving worker keeps the device busy. - print( - f"[stop_tasks] Could not terminate worker pid {row_pid}: {exc}" - ) + print(f"[stop_tasks] Could not terminate worker pid {row_pid}: {exc}") DeviceExecutionLock.cleanup_stale_locks() stopped = True return stopped, is_local_owner, reservation_cancelled @@ -1264,9 +1474,7 @@ class TaskQueueService: state.active_connections.pop(sid, None) if stopped_session_id: - session_repo.update_session_status( - str(stopped_session_id), "cancelled", time.time() - ) + session_repo.update_session_status(str(stopped_session_id), "cancelled", time.time()) try: is_mcp = bool(owner and owner.ingress == "mcp") if is_mcp or trace_store.read_status(str(stopped_session_id)): @@ -1287,8 +1495,7 @@ class TaskQueueService: ) if state.active_session_id and ( - not stopped_session_id - or str(state.active_session_id) == str(stopped_session_id) + not stopped_session_id or str(state.active_session_id) == str(stopped_session_id) ): state.active_session_id = None state.current_goal = None @@ -1305,8 +1512,7 @@ class TaskQueueService: ( item for item in state.queue_items - if isinstance(item, dict) - and str(item.get("session_id")) == str(stopped_session_id) + if isinstance(item, dict) and str(item.get("session_id")) == str(stopped_session_id) ), None, ) @@ -1316,15 +1522,12 @@ class TaskQueueService: item for item in state.queue_items if not ( - isinstance(item, dict) - and str(item.get("session_id")) == str(stopped_session_id) + isinstance(item, dict) and str(item.get("session_id")) == str(stopped_session_id) ) ] @classmethod - def _stop_targeted_task( - cls, target_sid: str | None, target_device: str | None - ) -> bool: + def _stop_targeted_task(cls, target_sid: str | None, target_device: str | None) -> bool: """Stop a specific task (or default single-device active task).""" active_owners = {} try: @@ -1338,9 +1541,7 @@ class TaskQueueService: owner_record_exists = DeviceExecutionLock.has_owner_record(target_device) owner_pid = owner.pid if owner else None - local_run_key, local_run, local_proc = cls._resolve_local_run( - target_sid, target_device - ) + local_run_key, local_run, local_proc = cls._resolve_local_run(target_sid, target_device) local_pid = getattr(local_proc, "pid", None) is_local_owner = bool(owner_pid and local_pid and owner_pid == local_pid) @@ -1409,7 +1610,6 @@ class TaskQueueService: return cls._stop_targeted_task(target_sid, target_device) - @classmethod def resume_task(cls) -> bool: if PAUSE_FILE.exists(): @@ -1417,6 +1617,40 @@ class TaskQueueService: return True return False + @classmethod + def recover_orphaned_recordings_on_launch(cls) -> int: + """Finalize recordings left behind by workers that died with the daemon. + + The per-run finalizer handles workers that exit while the daemon is up; + this sweep covers everything else (daemon crash, machine reboot, tasks + cancelled before this recovery path existed). Returns the number of + recordings published. + """ + recovered = 0 + try: + pending = session_repo.get_unfinalized_video_recordings() + except Exception as exc: + print(f"[ServerStartup] Could not enumerate unfinalized recordings: {exc}") + return 0 + for row in pending: + sess_id = row.get("session_id") + try: + final_path = media_service.recover_orphaned_recording( + row.get("local_video_path"), + row.get("start_time") or row.get("session_start_time"), + ) + except Exception as exc: + print(f"[ServerStartup] Recording recovery failed for {sess_id}: {exc}") + continue + if not final_path: + continue + if session_repo.mark_recording_ready(str(sess_id), str(final_path)): + recovered += 1 + print(f"[ServerStartup] Recovered recording for session {sess_id}: {final_path}") + if recovered: + print(f"[ServerStartup] Recovered {recovered} orphaned recording(s).") + return recovered + @staticmethod def archive_older_replays_on_launch(): """Archives all existing step replay output folders on server launch.""" diff --git a/apps/admin_console/services/worker_process_io.py b/apps/admin_console/services/worker_process_io.py index 6c77db0..f94ab82 100644 --- a/apps/admin_console/services/worker_process_io.py +++ b/apps/admin_console/services/worker_process_io.py @@ -47,9 +47,7 @@ def subprocess_creation_kwargs() -> dict[str, Any]: "stderr": asyncio.subprocess.STDOUT, } if sys.platform == "win32": - kwargs["creationflags"] = ( - subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW - ) + kwargs["creationflags"] = subprocess.CREATE_NEW_PROCESS_GROUP | subprocess.CREATE_NO_WINDOW return kwargs diff --git a/apps/showcase_ui/public/logo.svg b/apps/showcase_ui/public/logo.svg index 97def98..53a9a18 100644 --- a/apps/showcase_ui/public/logo.svg +++ b/apps/showcase_ui/public/logo.svg @@ -20,7 +20,7 @@ - + diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html index 0bb4fc2..b58e27d 100644 --- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html @@ -277,7 +277,7 @@ limitations under the License. } } - + @if (milestone.checks.length > 0) {
@for (check of milestone.checks; track trackMarkdownLine($index, check)) { @@ -505,7 +505,7 @@ limitations under the License. class="phase-header" [class.first-phase]="first && startupWorkItems().length === 0" > - Worked for {{ phase.durationSeconds }}s + {{ isCheckerPhase(phase) ? 'Checked' : 'Worked' }} for {{ phase.durationSeconds }}s @if (phase.tokens) { · @@ -524,12 +524,68 @@ limitations under the License. >
+ + @if (isCheckerBlock(block)) { +
+
+ + {{ getCheckerPhaseLabel(block) }} + @if (block.data.phase !== 'outcome' && block.data.subgoal_text) { + {{ block.data.subgoal_text }} + } + {{ getCheckerStatusLabel(block) }} +
+ @if (!isCheckerMuted(block) && getCheckerItems(block).length > 0) { +
    + @for (item of getCheckerItems(block); track $index) { +
  1. + {{ $index + 1 }} + {{ item.text }} + {{ getCheckMethodLabel(item, block) }} +
  2. + } +
+ } +
+ } @if (getSortedStepEvents(block.data).length > 0) {
@for (item of getSortedStepEvents(block.data); track trackStepEvent(itemIdx, item); let itemIdx = $index) { + + @if (isCheckerBlock(block) && item.type === 'thinking') { +
+
+ + Thought +
+
+
+
+
+
+
+ } + @if (isCheckerBlock(block) && item.type === 'text' && isCheckerTextVisible(item)) { +
+
+ + Work +
+
+
+
+
+ } - @if (item.type === 'thinking') { + @if (!isCheckerBlock(block) && item.type === 'thinking') {
} - @if (item.type === 'text' && getRawThinking(block)) { + @if (!isCheckerBlock(block) && item.type === 'text' && getRawThinking(block)) {
@@ -1093,6 +1149,26 @@ limitations under the License. }
} + + @if (hasCheckerResult(block)) { +
+ @for (verdict of getCheckerVerdicts(block); track $index) { +
+ {{ getVerdictItemNumber(block, verdict, $index) }} + {{ getVerdictIcon(verdict.status) }} + + {{ verdict.evidence || getVerdictStatusText(verdict.status) }} + @if (verdict.suggestion) { + {{ verdict.suggestion }} + } + +
+ } + @for (note of getCheckerNotes(block); track note) { +
{{ note }}
+ } +
+ }
} diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss index b12e560..9695191 100644 --- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss @@ -3558,5 +3558,162 @@ $stream-row-height: 25px; } } +/* Checker blocks share the step timeline, marked by a violet left border. */ +.agent-stream-container .stream-logs-content .phase-body .block-item.type-checker { + border-left: 2px solid #ddd6fe; + padding-left: 12px; + margin-left: -14px; + font-family: 'Google Sans', 'Google Sans Text', 'Inter', Arial, sans-serif; + .check-index { + flex: 0 0 14px; + text-align: right; + color: #a1a1aa; + font-variant-numeric: tabular-nums; + user-select: none; + } + .check-lane { + display: flex; + flex-direction: column; + gap: $uniform-gap; + margin-bottom: $uniform-gap; + + .check-header { + display: flex; + align-items: center; + gap: 8px; + min-height: 20px; + font-size: 12px; + line-height: 1.5; + letter-spacing: 0.2px; + color: #71717a; + user-select: none; + + .stream-status-dot { + flex: 0 0 auto; + width: 8px; + height: 8px; + border-radius: 50%; + display: inline-block; + background: #d4d4d8; + + &.streaming { + background: #7c3aed; + box-shadow: 0 0 0 0 rgba(124, 58, 237, 0.45); + animation: pulse-stream-dot 1.8s cubic-bezier(0.4, 0, 0.6, 1) infinite; + } + } + + .check-title { + flex: 0 0 auto; + font-weight: 500; + color: #52525b; + } + + .check-subject { + flex: 1 1 auto; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .check-status { + flex: 0 0 auto; + margin-left: auto; + font-weight: 500; + color: #52525b; + white-space: nowrap; + } + } + + &.tone-passed .check-status { color: #15803d; } + &.tone-failed .check-status { color: #b91c1c; } + &.tone-inconclusive .check-status { color: #b45309; } + &.tone-running .check-status { color: #6d28d9; } + &.tone-muted { opacity: 0.6; } + + .check-items { + list-style: none; + margin: 0; + padding: 0 0 0 16px; + display: flex; + flex-direction: column; + gap: 2px; + + .check-item { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 12px; + line-height: 1.5; + color: #3f3f46; + + .check-text { + flex: 1 1 auto; + min-width: 0; + word-break: break-word; + } + + .check-method { + flex: 0 0 auto; + color: #a1a1aa; + white-space: nowrap; + } + } + } + } + + .check-result { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: $uniform-gap; + padding-left: 16px; + + .check-verdict { + display: flex; + align-items: flex-start; + gap: 8px; + font-size: 12px; + line-height: 1.5; + color: #52525b; + + .check-index { + line-height: 18px; + } + + .verdict-icon { + flex: 0 0 auto; + font-size: 15px; + line-height: 18px; + color: #a1a1aa; + } + + &.passed .verdict-icon { color: #16a34a; } + &.failed .verdict-icon { color: #dc2626; } + &.inconclusive .verdict-icon { color: #d97706; } + &.superseded, &.unchecked { color: #a1a1aa; } + + .verdict-body { + flex: 1 1 auto; + min-width: 0; + word-break: break-word; + } + + .verdict-suggestion { + display: block; + color: #1d4ed8; + } + } + + .check-note { + font-size: 12px; + line-height: 1.5; + color: #71717a; + padding-left: 22px; + word-break: break-word; + } + } +} diff --git a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts index f8efb59..2c19fba 100644 --- a/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +++ b/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts @@ -470,6 +470,8 @@ export class AgentStreamComponent implements AfterViewInit { effect(() => { const blocks = this.consolidatedBlocks(); blocks.forEach(block => { + // Check blocks render their per-turn segments directly (no typewriter). + if (block.type === 'checker') return; const blockId = block.id; const rawText = this.getRawThinking(block) || ''; const nativeText = this.getNativeThinking(block) || ''; @@ -1162,8 +1164,187 @@ export class AgentStreamComponent implements AfterViewInit { return this.getThinkingTexts(block.data).raw; } + // --- Check blocks (verifier lane) ---------------------------------------------- + public isCheckerBlock(block: any): boolean { + return block?.type === 'checker'; + } + + public isCheckerPhase(phase: any): boolean { + const blocks: any[] = Array.isArray(phase?.blocks) ? phase.blocks : []; + return blocks.length > 0 && blocks.every((b) => this.isCheckerBlock(b)); + } + + public getCheckerPhaseLabel(block: any): string { + switch (block?.data?.phase) { + case 'final': + return 'Final check'; + case 'outcome': + return 'Result'; + default: + return 'Check'; + } + } + + public getCheckerVerdicts(block: any): any[] { + return Array.isArray(block?.data?.verdicts) ? block.data.verdicts : []; + } + + /** Declared check items; persisted attempts only carry verdicts, so derive them. */ + public getCheckerItems(block: any): any[] { + const d = block?.data || {}; + if (Array.isArray(d.items) && d.items.length > 0) return d.items; + return this.getCheckerVerdicts(block).map((v) => ({ kind: v.kind, text: v.item_text, when: v.when })); + } + + /** How the item is judged, in plain words (verify = must pass, assert = recorded test result). */ + public getCheckMethodLabel(item: any, block: any): string { + const parts: string[] = [item?.kind === 'assert' ? 'Test assertion' : 'Must pass']; + if (item?.when === 'at_end' && block?.data?.phase !== 'final') parts.push('at the end'); + return parts.join(' · '); + } + + /** Superseded / unchecked attempts collapse to their header line. */ + public isCheckerMuted(block: any): boolean { + const status = block?.data?.status; + return status === 'superseded' || status === 'unchecked'; + } + + /** Number of the check item a verdict belongs to (1-based, matches the list above the stream). */ + public getVerdictItemNumber(block: any, verdict: any, fallbackIndex: number): number { + const idx = this.getCheckerItems(block).findIndex((i) => i.text === verdict?.item_text); + return (idx > -1 ? idx : fallbackIndex) + 1; + } + + public getVerdictStatusText(status: string): string { + switch (status) { + case 'passed': + return 'Passed'; + case 'failed': + return 'Failed'; + case 'inconclusive': + return 'Could not be confirmed'; + case 'superseded': + return 'Superseded by a newer check'; + case 'unchecked': + return 'Not checked'; + default: + return status || ''; + } + } + + public hasCheckerResult(block: any): boolean { + if (!this.isCheckerBlock(block) || this.isBlockRunning(block) || this.isCheckerMuted(block)) return false; + if (block.data?.phase === 'outcome') return this.getCheckerNotes(block).length > 0; + return this.getCheckerVerdicts(block).length > 0 || this.getCheckerNotes(block).length > 0; + } + + /** Follow-ups not already visible in the verdict rows (the findings text repeats them). */ + public getCheckerNotes(block: any): string[] { + const d = block?.data || {}; + if (d.phase === 'outcome') return Array.isArray(d.last_findings) ? d.last_findings : []; + const notes: string[] = []; + if (Array.isArray(d.unmet_subgoals)) { + for (const text of d.unmet_subgoals) notes.push(`Not finished yet: ${text}`); + } + if (d.reverted) notes.push('The step was sent back to be redone.'); + if (d.error) notes.push(String(d.error)); + return notes; + } + + // Streamed turns of a check: the last one is live while the block runs; + // finished Thought turns fold unless the reader opens them. + public openCheckSegments = signal>(new Set()); + + public isCheckerSegmentRunning(block: any, itemIdx: number): boolean { + if (!this.isBlockRunning(block)) return false; + return itemIdx === this.getSortedStepEvents(block.data).length - 1; + } + + public isCheckerSegmentCollapsed(block: any, itemIdx: number): boolean { + if (this.isCheckerSegmentRunning(block, itemIdx)) return false; + return !this.openCheckSegments().has(`${block.id}-${itemIdx}`); + } + + public toggleCheckerSegment(block: any, itemIdx: number): void { + const key = `${block.id}-${itemIdx}`; + this.openCheckSegments.update((set) => { + const next = new Set(set); + if (next.has(key)) next.delete(key); else next.add(key); + return next; + }); + } + + public isCheckerTextVisible(item: any): boolean { + const text = item?.data?.text; + return typeof text === 'string' && text.trim().length > 0 && this.isHumanThinking(text); + } + + public getCheckerTone(block: any): string { + const d = block?.data || {}; + if (d.phase === 'outcome') { + return d.task_status === 'completed' ? 'passed' : (d.task_status === 'blocked' ? 'failed' : 'inconclusive'); + } + if (d.isCompleted === false) return 'running'; + if (d.status === 'superseded' || d.status === 'unchecked') return 'muted'; + if (d.status === 'error') return 'inconclusive'; + const verdicts = this.getCheckerVerdicts(block); + if (verdicts.some((v) => v.status === 'failed')) return 'failed'; + if (verdicts.some((v) => v.status === 'inconclusive')) return 'inconclusive'; + return 'passed'; + } + + public getCheckerStatusLabel(block: any): string { + const d = block?.data || {}; + if (d.phase === 'outcome') { + const t = d.tests || {}; + const label = d.task_status === 'completed' ? 'Goal completed' + : (d.task_status === 'blocked' ? 'Blocked' : 'Partially completed'); + const counts = [ + [t.passed, 'passed'], + [t.failed, 'failed'], + [t.inconclusive, 'inconclusive'], + [t.unchecked, 'not checked'] + ] + .filter(([n]) => Number(n) > 0) + .map(([n, word]) => `${n} ${word}`); + return counts.length > 0 ? `${label} · ${counts.join(' · ')}` : label; + } + if (d.isCompleted === false) return 'Checking…'; + switch (d.status) { + case 'superseded': + return 'Superseded'; + case 'unchecked': + return 'Not checked'; + case 'error': + return 'No verdict'; + } + const verdicts = this.getCheckerVerdicts(block); + const failed = verdicts.filter((v) => v.status === 'failed').length; + if (failed > 0) return `${failed} failed`; + if (verdicts.some((v) => v.status === 'inconclusive')) return 'Inconclusive'; + return verdicts.length > 0 ? 'Passed' : 'Done'; + } + + public getVerdictIcon(status: string): string { + switch (status) { + case 'passed': + return 'check_circle'; + case 'failed': + return 'cancel'; + case 'inconclusive': + return 'help'; + case 'superseded': + return 'history'; + case 'unchecked': + return 'radio_button_unchecked'; + default: + return 'pending'; + } + } + public hasVisibleContent(block: any): boolean { if (!block) return false; + if (block.type === 'checker') return true; const hasNative = Boolean(this.getNativeThinking(block)); const hasRaw = Boolean(this.getRawThinking(block)); const hasVisibleTools = Boolean(block.data?.generic_tools) && block.data.generic_tools.some((t: any) => @@ -1569,6 +1750,7 @@ export class AgentStreamComponent implements AfterViewInit { public isBlockRunning(block: any): boolean { if (!block || !block.data) return false; if (!this.isCurrentSessionActive()) return false; + if (block.type === 'checker') return block.data.isCompleted === false; if (this.isReportStatusAction(block.data.action_taken) || block.data?.status === 'completed' || block.data?.status === 'failed' || block.data?.status === 'cancelled') return false; if (block.data.isCompleted === false) return true; const blocks = this.consolidatedBlocks(); diff --git a/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html b/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html index 5d9d053..efd8882 100644 --- a/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +++ b/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html @@ -290,7 +290,7 @@ limitations under the License. }
} - + @if (milestone.checks.length > 0) {
@for (check of milestone.checks; track trackMarkdownLine($index, check)) { diff --git a/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts b/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts new file mode 100644 index 0000000..bb70e23 --- /dev/null +++ b/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts @@ -0,0 +1,47 @@ +import { + DEFAULT_EXPLORER_MODE, + DEFAULT_VERIFICATION_LEVEL, + EXPLORER_MODES, + VERIFICATION_LEVELS, + levelIndex, + notchPercent +} from './pro-tuning.model'; + +describe('pro tuning ladders', () => { + it('keeps every notch id and label unique within its ladder', () => { + for (const ladder of [VERIFICATION_LEVELS, EXPLORER_MODES]) { + expect(new Set(ladder.map((l) => l.id)).size).toBe(ladder.length); + expect(new Set(ladder.map((l) => l.label)).size).toBe(ladder.length); + } + }); + + it('mirrors the backend wire values', () => { + expect(VERIFICATION_LEVELS.map((l) => l.id)).toEqual(['off', 'final', 'checkpoints', 'strict']); + expect(EXPLORER_MODES.map((l) => l.id)).toEqual(['flash', 'pro', 'ultra']); + expect(VERIFICATION_LEVELS.map((l) => l.id)).toContain(DEFAULT_VERIFICATION_LEVEL); + expect(EXPLORER_MODES.map((l) => l.id)).toContain(DEFAULT_EXPLORER_MODE); + }); + + it('every level explains itself', () => { + for (const level of [...VERIFICATION_LEVELS, ...EXPLORER_MODES]) { + expect(level.tagline.length).toBeGreaterThan(10); + expect(level.latency.length).toBeGreaterThan(0); + expect(level.runs.length).toBeGreaterThan(0); + expect(level.bestFor.length).toBeGreaterThan(10); + } + }); + + it('resolves ids case-insensitively and falls back to the default', () => { + expect(levelIndex(VERIFICATION_LEVELS, ' STRICT ', DEFAULT_VERIFICATION_LEVEL)).toBe(3); + expect(levelIndex(VERIFICATION_LEVELS, 'nope', DEFAULT_VERIFICATION_LEVEL)).toBe(1); + expect(levelIndex(EXPLORER_MODES, null, DEFAULT_EXPLORER_MODE)).toBe(0); + }); + + it('maps notches onto the track', () => { + expect(notchPercent(0, 4)).toBe(0); + expect(notchPercent(3, 4)).toBe(100); + expect(notchPercent(1, 3)).toBe(50); + expect(notchPercent(9, 3)).toBe(100); + expect(notchPercent(0, 1)).toBe(0); + }); +}); diff --git a/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts b/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts new file mode 100644 index 0000000..9e57c3e --- /dev/null +++ b/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2026 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Launcher options for `/api/run` (`verification_level`, `explorer_mode`). + * Keep ids in sync with `VERIFICATION_LEVEL_PRESETS` in `artemis/config/agent.py` + * and `EXPLORER_TIERS` in `artemis/agents/explorer/tiers.py`. + */ + +export type VerificationLevelId = 'off' | 'final' | 'checkpoints' | 'strict'; +export type ExplorerModeId = 'flash' | 'pro' | 'ultra'; + +/** One notch on a tuning slider. */ +export interface TuningLevel { + /** Wire value sent to the backend. */ + id: TId; + /** Short name shown next to the slider title and as the hover card heading. */ + label: string; + /** One-sentence summary shown in the hover card. */ + tagline: string; + /** Plain-language time cost, e.g. "no extra time". */ + latency: string; + /** Checks or searches performed at this level. */ + runs: string[]; + /** Checks or searches omitted at this level. */ + skips?: string[]; + /** When to pick this level. */ + bestFor: string; +} + +export const VERIFICATION_LEVELS: readonly TuningLevel[] = [ + { + id: 'off', + label: 'Off', + tagline: 'No checking. The run ends as soon as the task looks done.', + latency: 'no extra time', + runs: [ + 'Each step is treated as finished the moment it is carried out.', + 'You get the full action trace, but no pass / fail verdict.' + ], + skips: ['Nothing is double-checked and nothing is retried.'], + bestFor: 'Quick tries and demos, when you only want to watch what happens.' + }, + { + id: 'final', + label: 'At the end', + tagline: 'One check of the finished result against your goal. This is the default.', + latency: 'adds about 20–60 s at the end', + runs: [ + 'When the task finishes, the final screen, the step history and the device state are compared with what you asked for.', + 'If the result does not match, the task goes back and tries to fix it, up to 3 times.' + ], + skips: ['Nothing is checked while the task is still running.'], + bestFor: 'Everyday tasks: an honest pass / fail without slowing the run down.' + }, + { + id: 'checkpoints', + label: 'Every step', + tagline: 'Each step is checked as soon as it is done, plus the final check.', + latency: 'a short check after each step, done in the background', + runs: [ + 'Every step is checked right after it completes, using the screenshots from that moment.', + 'If a step went wrong, it gets fixed before moving on (up to 2 tries per step).', + 'A failed test condition is written down and the task keeps going.', + 'The final check still runs at the end.' + ], + bestFor: 'Long tasks where one early mistake would spoil everything after it.' + }, + { + id: 'strict', + label: 'Strict', + tagline: 'Every step is checked, with more retries. The first failed test stops the run.', + latency: 'slowest: more checks and more retries', + runs: [ + 'Each check takes longer and gets more attempts: 4 fixes per step and 5 at the end.', + 'The first failed test condition stops the run immediately, with the evidence attached.' + ], + bestFor: 'Release checks and regression runs, where a wrong pass is never acceptable.' + } +]; + +export const EXPLORER_MODES: readonly TuningLevel[] = [ + { + id: 'flash', + label: 'Quick glance', + tagline: 'Finds buttons and text on the screen in a single look.', + latency: '1 look per search', + runs: [ + 'Something on screen is asked for by name, icon or colour and its position comes back straight away.', + 'Several things can be looked up at once.' + ], + skips: ['No zooming in and no second try.'], + bestFor: 'Ordinary apps with clearly labelled buttons, icons and text.' + }, + { + id: 'pro', + label: 'Second look', + tagline: 'Takes up to 3 looks, thinking in between, before answering.', + latency: 'up to 3 looks per search', + runs: [ + 'The screen layout is read first, then the picture is searched.', + 'If the first try misses, a different approach is tried within the 3 looks.' + ], + skips: ['Still no zooming into small areas, to keep searches short.'], + bestFor: 'Things described by where they are ("the switch next to Wi-Fi") or with unclear labels.' + }, + { + id: 'ultra', + label: 'Close-up', + tagline: 'Zooms into parts of the screen and takes up to 8 looks.', + latency: 'up to 8 looks per search (slowest)', + runs: [ + 'Parts of the screen can be cropped and magnified to read tiny text and crowded layouts piece by piece.', + 'Later looks reuse the earlier ones, so they cost less time than they sound.' + ], + bestFor: 'Crowded screens, tiny targets, charts and drawings, and checks where exact placement matters.' + } +]; + +/** Per-run tuning sent with `/api/run` for the Pro profile. */ +export interface ProTuningOptions { + verificationLevel?: VerificationLevelId | string; + explorerMode?: ExplorerModeId | string; +} + +/** Effective defaults reported by `GET /api/run/defaults`. */ +export interface ProTuningDefaults { + verification_level?: string | null; + explorer_mode?: string | null; +} + +export const DEFAULT_VERIFICATION_LEVEL: VerificationLevelId = 'final'; +export const DEFAULT_EXPLORER_MODE: ExplorerModeId = 'flash'; + +/** Index of a level id within its ladder; falls back to the default when unknown. */ +export function levelIndex( + ladder: readonly TuningLevel[], + id: string | null | undefined, + fallback: TId +): number { + const wanted = String(id ?? '').trim().toLowerCase(); + const idx = ladder.findIndex((l) => l.id === wanted); + if (idx >= 0) return idx; + return Math.max(0, ladder.findIndex((l) => l.id === fallback)); +} + +/** Slider fill percentage for a notch index on a ladder of `count` notches. */ +export function notchPercent(index: number, count: number): number { + if (count <= 1) return 0; + const clamped = Math.min(Math.max(index, 0), count - 1); + return (clamped / (count - 1)) * 100; +} diff --git a/apps/showcase_ui/src/app/core/models/stream.model.ts b/apps/showcase_ui/src/app/core/models/stream.model.ts index d66042f..0790bd7 100644 --- a/apps/showcase_ui/src/app/core/models/stream.model.ts +++ b/apps/showcase_ui/src/app/core/models/stream.model.ts @@ -79,7 +79,7 @@ export interface StepItemData { export interface StepBlock { id: string; - type: 'llm_stream' | 'step'; + type: 'llm_stream' | 'step' | 'checker'; timestamp: string; data: any; } @@ -93,6 +93,75 @@ export interface PhaseBlock { blocks: StepBlock[]; } +/** One check item's verdict as booked by the Checker (ledger record shape). */ +export interface CheckerVerdict { + item_text: string; + kind: 'verify' | 'assert' | string; + status: 'passed' | 'failed' | 'inconclusive' | 'superseded' | 'unchecked' | string; + evidence: string; + suggestion?: string; +} + +export interface CheckerCheckItem { + kind: string; + text: string; + when?: string; +} + +/** + * One streamed LLM turn of a multi-turn agent (the Checker's tool loop): + * the text of one execution id with the time its first chunk arrived, so the + * timeline can interleave it with the tool calls that followed. + */ +export interface StreamSegment { + execution_id: string; + stream_type: 'thinking' | 'text'; + text: string; + timestamp: string; + isCompleted?: boolean; +} + +/** + * Data of a `checker` timeline block: one Checker attempt (a midway checkpoint + * of a completed subgoal or the exit final review), or the run outcome + * (`phase: 'outcome'`). Streamed reasoning and tool traces land in the same + * `operator_*_thinking` / `generic_tools` fields as an Operator step so the + * timeline renders them with one code path. + */ +export interface CheckerBlockData { + event?: string; + attempt_id?: string; + checkpoint_id?: string; + phase: 'checkpoint' | 'final' | 'outcome' | string; + subgoal_text?: string; + status?: 'running' | 'done' | 'superseded' | 'unchecked' | 'error' | string; + trace_id?: string | null; + anchor_step_id?: string | null; + items?: CheckerCheckItem[]; + verdicts?: CheckerVerdict[]; + findings?: string[]; + unmet_subgoals?: string[]; + reverted?: boolean; + applicable?: boolean; + repairs_used?: number; + route?: string; + error?: string; + task_status?: 'completed' | 'partial' | 'blocked' | string; + tests?: { passed: number; failed: number; inconclusive: number; unchecked: number }; + last_findings?: string[]; + started_at?: number; + finished_at?: number; + duration?: number; + isCompleted?: boolean; + generic_tools?: any[]; + /** Per-turn stream segments (live sessions only); the flat fields below hold the joined text. */ + stream_segments?: StreamSegment[]; + operator_native_thinking?: string; + operator_raw_thinking?: string; + [key: string]: any; +} + +/** @deprecated legacy JSON-in-stream checker verdict (pre-ledger); kept for the parser. */ export interface CheckerResult { success: boolean; reason: string; diff --git a/apps/showcase_ui/src/app/pages/home/home.component.html b/apps/showcase_ui/src/app/pages/home/home.component.html index 8848136..e2c6819 100644 --- a/apps/showcase_ui/src/app/pages/home/home.component.html +++ b/apps/showcase_ui/src/app/pages/home/home.component.html @@ -580,7 +580,7 @@ {{ modelConfigEnv()?.default_model?.provider || 'google' }} / - {{ modelConfigEnv()?.default_model?.model || 'gemini-3.7-flash' }} + {{ modelConfigEnv()?.default_model?.model || 'gemini-3.8-flash' }}
@@ -1761,14 +1761,14 @@

Flash · - Token-efficient, rapid 3–5s/step reactive decisions. Best for routine daily tasks within 25 steps. - (No long-term monitoring or video analysis) + Token-efficient, rapid 3–5s/step decisions straight from the live screen. Best for everyday tasks with a clear path, however long they run. + (No plan, no result check, no ADB commands)

} @else {

Pro · - Our most powerful agent architecture. Handles 100+ step workflows and direct phone state monitoring (20–40s / step). + Our most powerful mode. Plans first, checks its own results and recovers when a tap is blocked. Handles 100+ step workflows and live phone monitoring (15–40s / step).

} @@ -1792,7 +1792,7 @@ class="model-toggle-btn btn-flash" [class.active]="selectedProfile() === 'flash'" (click)="setProfile('flash')" - title="Flash: Reactive fast-loop architecture for standard app workflows (3–5s/step)" + title="Flash: fast single-look loop for everyday app tasks (3–5s/step)" > bolt Flash @@ -1802,7 +1802,7 @@ class="model-toggle-btn btn-pro" [class.active]="selectedProfile() === 'pro'" (click)="setProfile('pro')" - title="Pro: Multi-agent graph architecture with planner & closed-loop verification for complex tasks" + title="Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks (15–40s/step)" > psychology Pro @@ -1863,6 +1863,120 @@ } + + +
+
+ Pro settings + @if (!isTuningDefault()) { + + } +
+ +
+ @for (slider of tuningSliders(); track slider.kind) { +
+
+ {{ slider.name }} + {{ slider.level.label }} +
+ + +
+ + + @if (slider.index === slider.ladder.length - 1) { + + } + + + @if (activeTuningTip() === slider.kind) { + + + } +
+
+ } +
+
} diff --git a/apps/showcase_ui/src/app/pages/home/home.component.scss b/apps/showcase_ui/src/app/pages/home/home.component.scss index 1eda84e..a6c6921 100644 --- a/apps/showcase_ui/src/app/pages/home/home.component.scss +++ b/apps/showcase_ui/src/app/pages/home/home.component.scss @@ -4272,6 +4272,381 @@ $text-light: #94a3b8; } } + + /* Pro tuning sliders and hover cards */ + .pro-tuning-card { + --tip-w: 320px; + --thumb-w: 22px; + --thumb-h: 14px; + --col-gap: 28px; + --accent: #6366f1; + position: relative; + background: rgba(255, 255, 255, 0.88); + backdrop-filter: blur(20px) saturate(180%); + -webkit-backdrop-filter: blur(20px) saturate(180%); + border: 1px solid rgba(226, 232, 240, 0.95); + border-radius: 16px; + padding: 12px 18px 14px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.02), 0 8px 24px -6px rgba(15, 23, 42, 0.04); + display: flex; + flex-direction: column; + gap: 10px; + animation: slideUpFade 0.2s cubic-bezier(0.4, 0, 0.2, 1); + + &.tip-open { + z-index: 30; + } + + .tuning-card-header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 10px; + min-height: 16px; + + .tuning-title { + font-size: 13px; + font-weight: 700; + color: #0f172a; + letter-spacing: -0.01em; + } + + .btn-tuning-reset { + border: none; + background: transparent; + padding: 0; + font-family: inherit; + font-size: 12px; + font-weight: 500; + color: #94a3b8; + cursor: pointer; + transition: color 0.15s ease; + + &:hover { + color: #475569; + } + + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 3px; + border-radius: 3px; + } + } + } + + .tuning-sliders { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 4px var(--col-gap); + } + + .tuning-slider { + display: flex; + flex-direction: column; + gap: 4px; + min-width: 0; + /* Hover card clamp range, relative to this slider's track. The left + column may spill right across the gap; the right column may spill + left. Overridden below when the columns stack. */ + --tip-min: 0px; + --tip-max: calc(100% - var(--tip-w)); + + &.verify { + --tip-max: calc(200% + var(--col-gap) - var(--tip-w)); + } + + &.explore { + --accent: #0ea5e9; + --tip-min: calc(-100% - var(--col-gap)); + } + + .slider-head { + display: flex; + align-items: baseline; + gap: 8px; + font-size: 13px; + line-height: 18px; + color: #0f172a; + + .slider-name { + font-weight: 400; + color: #334155; + } + + .slider-value { + font-weight: 600; + } + } + + .slider-ends { + display: flex; + justify-content: space-between; + font-size: 11.5px; + line-height: 14px; + color: #94a3b8; + } + + .slider-track-wrap { + position: relative; + height: 26px; + display: flex; + align-items: center; + } + + .slider-rail { + position: relative; + width: 100%; + height: 6px; + border-radius: 3px; + background: #e2e8f0; + } + + .slider-fill { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: calc(var(--pct) * (100% - var(--thumb-w)) + var(--thumb-w) / 2); + border-radius: 3px; + background: color-mix(in srgb, var(--accent) 24%, #e2e8f0); + transition: width 0.15s ease; + } + + .slider-notch { + position: absolute; + top: 50%; + left: calc(var(--n) * (100% - var(--thumb-w)) + var(--thumb-w) / 2); + width: 4px; + height: 4px; + border-radius: 50%; + background: #94a3b8; + transform: translate(-50%, -50%); + transition: background 0.15s ease; + z-index: 1; + + &.reached { + background: rgba(255, 255, 255, 0.95); + } + } + + .slider-thumb { + position: absolute; + top: 50%; + left: calc(var(--pct) * (100% - var(--thumb-w)) + var(--thumb-w) / 2); + width: var(--thumb-w); + height: var(--thumb-h); + border-radius: 5px; + background: #ffffff; + border: 1px solid rgba(15, 23, 42, 0.1); + box-shadow: 0 1px 2px rgba(15, 23, 42, 0.12), 0 2px 6px rgba(15, 23, 42, 0.08); + transform: translate(-50%, -50%); + pointer-events: none; + transition: left 0.15s ease, box-shadow 0.15s ease, border-color 0.15s ease; + } + + /* End of track: a pixel front sweeps from the thumb to the left end, + a solid fill follows it until the whole rail has changed colour, + and the thumb emits one ripple. */ + .slider-solid { + position: absolute; + inset: 0; + border-radius: 3px; + background: var(--accent); + transform-origin: right center; + transform: scaleX(1); + animation: solidSweep 1s cubic-bezier(0.4, 0, 0.2, 1) 0.2s both; + pointer-events: none; + } + + .slider-dither { + position: absolute; + inset: 0; + display: block; + overflow: hidden; + border-radius: 3px; + pointer-events: none; + + rect { + fill: var(--accent); + animation: ditherIn 0.3s ease-out both; + } + } + + .thumb-ripple { + position: absolute; + top: 50%; + left: calc(var(--pct) * (100% - var(--thumb-w)) + var(--thumb-w) / 2); + width: var(--thumb-w); + height: var(--thumb-w); + border-radius: 50%; + border: 1.5px solid var(--accent); + transform: translate(-50%, -50%) scale(0.6); + opacity: 0; + pointer-events: none; + animation: thumbRipple 0.6s ease-out forwards; + } + + &.dragging .slider-thumb { + border-color: color-mix(in srgb, var(--accent) 45%, transparent); + box-shadow: + 0 1px 2px rgba(15, 23, 42, 0.12), + 0 2px 8px rgba(15, 23, 42, 0.1), + 0 0 0 4px color-mix(in srgb, var(--accent) 14%, transparent); + } + + /* Native range input does drag, keyboard, and a11y; it is invisible + but its thumb geometry matches the drawn thumb so hits line up. */ + .slider-input { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + opacity: 0; + cursor: pointer; + -webkit-appearance: none; + appearance: none; + background: transparent; + + &::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: var(--thumb-w); + height: 26px; + } + + &::-moz-range-thumb { + width: var(--thumb-w); + height: 26px; + border: 0; + } + } + + .slider-input:focus-visible ~ .slider-thumb, + &:has(.slider-input:focus-visible) .slider-thumb { + box-shadow: + 0 1px 2px rgba(15, 23, 42, 0.12), + 0 0 0 2px #ffffff, + 0 0 0 4px var(--accent); + } + + .tuning-tip-arrow { + position: absolute; + bottom: calc(100% + 2px); + left: calc(var(--pct) * (100% - var(--thumb-w)) + var(--thumb-w) / 2); + width: 10px; + height: 10px; + background: #ffffff; + border-right: 1px solid rgba(226, 232, 240, 1); + border-bottom: 1px solid rgba(226, 232, 240, 1); + transform: translate(-50%, 50%) rotate(45deg); + z-index: 41; + pointer-events: none; + } + + .tuning-tip { + position: absolute; + bottom: calc(100% + 7px); + left: clamp( + var(--tip-min), + calc(var(--pct) * (100% - var(--thumb-w)) + var(--thumb-w) / 2 - var(--tip-w) / 2), + var(--tip-max) + ); + width: var(--tip-w); + max-width: calc(100vw - 32px); + padding: 12px 14px; + background: #ffffff; + border: 1px solid rgba(226, 232, 240, 1); + border-radius: 12px; + box-shadow: 0 12px 32px -8px rgba(15, 23, 42, 0.18), 0 2px 6px rgba(15, 23, 42, 0.06); + z-index: 40; + pointer-events: none; + animation: tipPop 0.16s ease-out; + font-size: 12px; + line-height: 1.5; + color: #334155; + display: flex; + flex-direction: column; + gap: 8px; + + .tip-heading { + display: flex; + flex-direction: column; + gap: 1px; + + .tip-title { + font-size: 13.5px; + font-weight: 600; + color: #0f172a; + letter-spacing: -0.01em; + } + + .tip-latency { + font-size: 11.5px; + color: #94a3b8; + } + } + + .tip-tagline { + margin: 0; + color: #0f172a; + } + + .tip-section { + display: flex; + flex-direction: column; + gap: 2px; + + ul { + margin: 0; + padding-left: 16px; + display: flex; + flex-direction: column; + gap: 2px; + } + + li::marker { + color: #cbd5e1; + } + + &.skips li { + color: #64748b; + } + } + + .tip-section-title { + font-size: 10.5px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: #94a3b8; + } + + .tip-best { + display: flex; + flex-direction: column; + gap: 2px; + color: #475569; + } + } + } + + @media (max-width: 760px) { + .tuning-sliders { + grid-template-columns: minmax(0, 1fr); + gap: 10px; + } + + .tuning-slider { + &.verify, + &.explore { + --tip-min: 0px; + --tip-max: calc(100% - var(--tip-w)); + } + } + } + } + /* Smart Suggestions Hub (Device-Aware, Flash vs Pro Tailored) */ .smart-suggestions-container { display: flex; @@ -4733,7 +5108,47 @@ $text-light: #94a3b8; @media (prefers-reduced-motion: reduce) { .home-container .aurora-background .glow, .sparkle-icon, - .status-dot { + .status-dot, + .slider-dither rect, + .slider-solid, + .thumb-ripple { animation: none !important; } } + +@keyframes ditherIn { + from { + opacity: 0; + } +} + +@keyframes solidSweep { + from { + transform: scaleX(0); + } + to { + transform: scaleX(1); + } +} + +@keyframes thumbRipple { + 0% { + opacity: 0.7; + transform: translate(-50%, -50%) scale(0.6); + } + 100% { + opacity: 0; + transform: translate(-50%, -50%) scale(2.6); + } +} + +@keyframes tipPop { + from { + opacity: 0; + transform: translateY(6px) scale(0.96); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} diff --git a/apps/showcase_ui/src/app/pages/home/home.component.ts b/apps/showcase_ui/src/app/pages/home/home.component.ts index ff6a2ba..e189b9c 100644 --- a/apps/showcase_ui/src/app/pages/home/home.component.ts +++ b/apps/showcase_ui/src/app/pages/home/home.component.ts @@ -15,7 +15,6 @@ */ import { Component, signal, computed, effect, inject, OnInit, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; - import { FormsModule } from '@angular/forms'; import { Router, RouterLink } from '@angular/router'; import { AgentService } from '../../services/agent.service'; @@ -32,6 +31,69 @@ import { SuggestionCategory } from '../../core/data/smart-tasks.data'; import { TaskRecommendationService } from '../../core/services/task-recommendation.service'; +import { + DEFAULT_EXPLORER_MODE, + DEFAULT_VERIFICATION_LEVEL, + EXPLORER_MODES, + ExplorerModeId, + TuningLevel, + VERIFICATION_LEVELS, + VerificationLevelId, + levelIndex, + notchPercent +} from '../../core/models/pro-tuning.model'; + +export type TuningKind = 'verify' | 'explore'; + +/** One 2x2 px square of the "maxed out" dither texture drawn over a slider rail. */ +export interface DitherPixel { + /** Horizontal position as a percentage of the rail width. */ + x: number; + /** Row offset in px (rail is 6 px tall, three 2 px rows). */ + y: number; + /** Resting opacity; squares near the thumb are stronger. */ + opacity: number; + /** Animation delay in ms so the texture spreads leftwards from the thumb. */ + delay: number; +} + +/** + * Seeded pixels keep the slider texture stable across renders. + * Delays increase with distance from the thumb to animate from right to left. + */ +function buildDitherPixels(count = 260, seed = 7): DitherPixel[] { + let state = seed >>> 0; + const rand = (): number => { + state = (Math.imul(state, 1664525) + 1013904223) >>> 0; + return state / 0x100000000; + }; + const pixels: DitherPixel[] = []; + for (let i = 0; i < count; i++) { + const x = rand() * 100; + const y = Math.floor(rand() * 3) * 2; + pixels.push({ + x: Math.round(x * 10) / 10, + y, + opacity: Math.round((0.35 + 0.4 * rand()) * 100) / 100, + // 8 ms per percent: the front takes ~0.8 s to reach the left end. + delay: Math.round((100 - x) * 8) + }); + } + return pixels; +} + +/** Shared view model for the verification and screen-reading sliders. */ +export interface TuningSliderVm { + kind: TuningKind; + name: string; + /** One-word meaning of each end of the track, e.g. ["Off", "Strict"]. */ + ends: readonly [string, string]; + ladder: readonly TuningLevel[]; + index: number; + level: TuningLevel; + /** 0..1 position of the thumb along the track. */ + fraction: number; +} export type { AppReference, SmartSuggestion, SuggestionCategory }; @@ -139,6 +201,118 @@ export class HomeComponent implements OnInit, OnDestroy { public enableOutputter = signal(true); public showOutputterDrawer = signal(false); + // Slider indexes map to the API ids in VERIFICATION_LEVELS / EXPLORER_MODES. + public readonly verificationLevels = VERIFICATION_LEVELS; + public readonly explorerModes = EXPLORER_MODES; + public verificationIndex = signal( + levelIndex(VERIFICATION_LEVELS, DEFAULT_VERIFICATION_LEVEL, DEFAULT_VERIFICATION_LEVEL) + ); + public explorerIndex = signal( + levelIndex(EXPLORER_MODES, DEFAULT_EXPLORER_MODE, DEFAULT_EXPLORER_MODE) + ); + /** Effective defaults from the backend config (`GET /api/run/defaults`). */ + private tuningDefaults = signal<{ verification: VerificationLevelId; explorer: ExplorerModeId }>({ + verification: DEFAULT_VERIFICATION_LEVEL, + explorer: DEFAULT_EXPLORER_MODE + }); + /** True once the user moved a slider; backend defaults then stop overriding it. */ + private tuningTouched = signal(false); + /** Which slider's hover card is open (while hovering, dragging, or focused). */ + public activeTuningTip = signal(null); + /** Pixel cloud drawn over a rail once its slider reaches the last notch. */ + public readonly ditherPixels: readonly DitherPixel[] = buildDitherPixels(); + + public verificationLevel = computed>( + () => VERIFICATION_LEVELS[this.verificationIndex()] + ); + public explorerMode = computed>( + () => EXPLORER_MODES[this.explorerIndex()] + ); + public isTuningDefault = computed(() => { + const d = this.tuningDefaults(); + return this.verificationLevel().id === d.verification && this.explorerMode().id === d.explorer; + }); + public tuningSliders = computed(() => { + const vi = this.verificationIndex(); + const ei = this.explorerIndex(); + return [ + { + kind: 'verify', + name: 'Result check', + ends: ['Off', 'Strict'], + ladder: VERIFICATION_LEVELS, + index: vi, + level: VERIFICATION_LEVELS[vi], + fraction: notchPercent(vi, VERIFICATION_LEVELS.length) / 100 + }, + { + kind: 'explore', + name: 'Screen reading', + ends: ['Faster', 'Sharper'], + ladder: EXPLORER_MODES, + index: ei, + level: EXPLORER_MODES[ei], + fraction: notchPercent(ei, EXPLORER_MODES.length) / 100 + } + ]; + }); + + public notchFraction(index: number, count: number): number { + return notchPercent(index, count) / 100; + } + + public setTuningIndex(kind: TuningKind, raw: number | string): void { + const idx = Math.round(Number(raw)); + if (!Number.isFinite(idx)) return; + const ladder = kind === 'verify' ? VERIFICATION_LEVELS : EXPLORER_MODES; + const clamped = Math.min(Math.max(idx, 0), ladder.length - 1); + this.tuningTouched.set(true); + if (kind === 'verify') { + this.verificationIndex.set(clamped); + } else { + this.explorerIndex.set(clamped); + } + // Keyboard nudges and drags should keep the explanation visible. + this.activeTuningTip.set(kind); + } + + public showTuningTip(kind: TuningKind): void { + this.activeTuningTip.set(kind); + } + + public hideTuningTip(kind: TuningKind): void { + if (this.activeTuningTip() === kind) { + this.activeTuningTip.set(null); + } + } + + public resetTuning(): void { + const d = this.tuningDefaults(); + this.verificationIndex.set(levelIndex(VERIFICATION_LEVELS, d.verification, DEFAULT_VERIFICATION_LEVEL)); + this.explorerIndex.set(levelIndex(EXPLORER_MODES, d.explorer, DEFAULT_EXPLORER_MODE)); + this.tuningTouched.set(false); + } + + /** Pull the effective config defaults so the sliders start where artemis.jsonc is. */ + private loadProTuningDefaults(): void { + this.agentService.getProTuningDefaults().subscribe({ + next: (res) => { + const vIdx = levelIndex(VERIFICATION_LEVELS, res?.verification_level, DEFAULT_VERIFICATION_LEVEL); + const eIdx = levelIndex(EXPLORER_MODES, res?.explorer_mode, DEFAULT_EXPLORER_MODE); + this.tuningDefaults.set({ + verification: VERIFICATION_LEVELS[vIdx].id, + explorer: EXPLORER_MODES[eIdx].id + }); + if (!this.tuningTouched()) { + this.verificationIndex.set(vIdx); + this.explorerIndex.set(eIdx); + } + }, + // Defaults are a convenience; the built-in ladder defaults already apply. + error: () => undefined + }); + } + public toggleOutputterDrawer(): void { this.showOutputterDrawer.update((v) => !v); } @@ -362,6 +536,8 @@ export class HomeComponent implements OnInit, OnDestroy { } ngOnInit(): void { + + this.loadProTuningDefaults(); // Initial fetch of system readiness & model configuration this.systemService.fetchReadiness().subscribe(); this.systemService.fetchModelConfigEnv().subscribe(); @@ -913,7 +1089,10 @@ export class HomeComponent implements OnInit, OnDestroy { this.selectedProfile() === 'pro' && this.expectedOutput().trim() ? this.expectedOutput().trim() : undefined, - this.selectedProfile() === 'pro' ? this.enableOutputter() : undefined + this.selectedProfile() === 'pro' ? this.enableOutputter() : undefined, + this.selectedProfile() === 'pro' + ? { verificationLevel: this.verificationLevel().id, explorerMode: this.explorerMode().id } + : undefined ) .subscribe({ next: () => { diff --git a/apps/showcase_ui/src/app/pages/workspace/workspace.component.html b/apps/showcase_ui/src/app/pages/workspace/workspace.component.html index 05525fa..a44cd84 100644 --- a/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +++ b/apps/showcase_ui/src/app/pages/workspace/workspace.component.html @@ -105,8 +105,8 @@ limitations under the License. rows="1" [disabled]="isSubmitting()" [placeholder]="selectedProfile() === 'pro' - ? 'Pro: Multi-agent cognitive graph architecture for complex multi-step tasks' - : 'Flash: Reactive fast loop for standard app workflows within 25 steps'" + ? 'Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks' + : 'Flash: fast single-look loop for everyday app tasks'" class="dock-textarea" spellcheck="false" > @@ -155,13 +155,13 @@ limitations under the License.
diff --git a/apps/showcase_ui/src/app/services/agent.service.ts b/apps/showcase_ui/src/app/services/agent.service.ts index 33220d7..88055d4 100644 --- a/apps/showcase_ui/src/app/services/agent.service.ts +++ b/apps/showcase_ui/src/app/services/agent.service.ts @@ -19,6 +19,7 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { Session, ModelInfo, TaskQueueItem, AgentStatusResponse } from '../core/models/session.model'; +import { ProTuningDefaults, ProTuningOptions } from '../core/models/pro-tuning.model'; import { StepItemData, StepReplayFrame } from '../core/models/stream.model'; import { extractStepReplayFrames } from '../utils/action-formatter.util'; export type { Session, ModelInfo, TaskQueueItem, AgentStatusResponse, StepItemData, StepReplayFrame }; @@ -347,7 +348,7 @@ export class AgentService { // Streaming chunks are coalesced into one signal update per short window so a // chatty LLM stream cannot force a full recompute pass per SSE chunk. - private pendingStreamChunks = new Map(); + private pendingStreamChunks = new Map(); private streamFlushTimer: ReturnType | null = null; private lastQueueSignature: string | null = null; private lastActiveTasksSignature: string | null = null; @@ -378,6 +379,14 @@ export class AgentService { return this.sessions().some(s => s.status === 'running' || s.status === 'paused'); }); + /** + * Effective Pro tuning defaults (verification level, explorer mode) from the + * backend agent config, so the launcher sliders start where the config is. + */ + public getProTuningDefaults(): Observable { + return this.http.get('/api/run/defaults'); + } + /** * Run a new task by submitting to backend queue */ @@ -385,7 +394,8 @@ export class AgentService { goal: string, profile: string = 'flash', expectedOutput?: string, - enableOutputter?: boolean + enableOutputter?: boolean, + proTuning?: ProTuningOptions ): Observable { return new Observable((obs) => { const submittedEvent: StartupProgressEvent = { @@ -401,6 +411,12 @@ export class AgentService { if (enableOutputter !== undefined) { payload.enable_outputter = enableOutputter; } + if (proTuning?.verificationLevel) { + payload.verification_level = proTuning.verificationLevel; + } + if (proTuning?.explorerMode) { + payload.explorer_mode = proTuning.explorerMode; + } this.clearUserPinnedSession(); this.http.post('/api/run', payload).subscribe({ next: (res) => { @@ -702,6 +718,7 @@ export class AgentService { this.isPaused.set(false); this.pausedError.set(null); this.fetchNotes(sessionId); + this.fetchChecks(sessionId); this.updateModelForCurrentView(); // If video window is currently open, dynamically sync/refresh video for new session @@ -709,7 +726,7 @@ export class AgentService { this.openVideoPlayer(sessionId); } - console.log(`Selecting session: ${sessionId}`); + console.debug(`Selecting session: ${sessionId}`); // Start reading the persisted snapshot immediately this.backfillSessionSteps(sessionId, loadGeneration); this.ensureLiveStream(); @@ -724,7 +741,7 @@ export class AgentService { return; } - console.log('Establishing persistent unified live stream via /api/stream'); + console.debug('Establishing persistent unified live stream via /api/stream'); // The stream is registered outside the Angular zone: high-frequency SSE // callbacks must not schedule a change-detection pass each. Signal writes // still notify the render scheduler, so the UI stays live. @@ -756,7 +773,8 @@ export class AgentService { 'session_started', 'session_ended', 'recording_ready', - 'recording_failed' + 'recording_failed', + 'checker_event' ]; eventTypes.forEach((eventType) => { @@ -843,6 +861,11 @@ export class AgentService { this.fetchStatus(); if (endedId) { this.fetchNotes(endedId); + if (String(endedId) === String(this.currentSessionId() || '')) { + // The exit final review / run outcome may have landed while the + // stream was reconnecting: reconcile from the persisted ledger. + this.fetchChecks(endedId); + } if ( this.isVideoWindowOpen() && this.activeVideoSessionId === endedId @@ -1010,14 +1033,19 @@ export class AgentService { private queueStreamChunk(parsedData: any): void { const execId = parsedData.execution_id; const stepId = parsedData.step_id; + // The emitting agent's trace: lets the aggregator route a concurrent + // agent's stream (e.g. the Checker) to its own block instead of the + // Operator step that happens to be current. + const parentTraceId = parsedData.parent_trace_id || null; const streamType = parsedData.stream_type || 'text'; const key = `${execId}|${streamType}`; const pending = this.pendingStreamChunks.get(key); if (pending) { pending.chunk += parsedData.chunk; if (stepId) pending.stepId = stepId; + if (parentTraceId) pending.parentTraceId = parentTraceId; } else { - this.pendingStreamChunks.set(key, { execId, stepId, streamType, chunk: parsedData.chunk }); + this.pendingStreamChunks.set(key, { execId, stepId, parentTraceId, streamType, chunk: parsedData.chunk }); } if (!this.streamFlushTimer) { const delay = typeof document !== 'undefined' && document.hidden ? 500 : 80; @@ -1058,14 +1086,18 @@ export class AgentService { data: { ...existingLog.data, text: existingLog.data.text + batch.chunk, - step_id: batch.stepId || existingLog.data.step_id + step_id: batch.stepId || existingLog.data.step_id, + parent_trace_id: batch.parentTraceId || existingLog.data.parent_trace_id || null } }; } else { + // A new execution closes the previous open stream of the same agent + // lane only; concurrent agents (Operator vs. Checker) keep theirs. next = next.map((l) => { if (l.type === 'llm_stream' && (l.data.stream_type || 'text') === batch.streamType && - !l.data.isCompleted) { + !l.data.isCompleted && + (l.data.parent_trace_id || null) === (batch.parentTraceId || null)) { return { ...l, data: { ...l.data, isCompleted: true } }; } return l; @@ -1076,6 +1108,7 @@ export class AgentService { data: { execution_id: batch.execId, step_id: batch.stepId, + parent_trace_id: batch.parentTraceId || null, text: batch.chunk, stream_type: batch.streamType, isCompleted: false @@ -1511,6 +1544,105 @@ export class AgentService { /** * Fetch notes for a specific session */ + /** + * Backfill the Checker's attempts and run outcome from the persisted verdict + * ledger (`/api/sessions/{id}/checks`) as synthetic `checker_event` logs, so + * historical sessions show the same timeline blocks as live ones. Live + * events for attempts already known are merged by attempt id in the + * aggregator, so re-fetching is idempotent. + */ + public fetchChecks(sessionId: string): void { + if (!sessionId) return; + this.http.get<{ records?: any[]; run_outcome?: any }>(`/api/sessions/${sessionId}/checks`).subscribe({ + next: (res) => { + if (this.currentSessionId() !== sessionId) return; + const snapshot = this.buildCheckerSnapshotLogs(sessionId, res?.records || [], res?.run_outcome || null); + this.sessionLogs.update((logs) => [ + ...logs.filter((log) => !log.checks_snapshot), + ...snapshot + ]); + }, + error: (err) => { + console.error(`Failed to fetch checks for session ${sessionId}:`, err); + } + }); + } + + private buildCheckerSnapshotLogs(sessionId: string, records: any[], runOutcome: any): any[] { + const byAttempt = new Map(); + for (const rec of records) { + if (!rec || !rec.attempt_id) continue; + const attemptId = String(rec.attempt_id); + const checkpointId = String(rec.checkpoint_id || ''); + let attempt = byAttempt.get(attemptId); + if (!attempt) { + attempt = { + event: 'attempt_finished', + phase: checkpointId === 'final' ? 'final' : 'checkpoint', + attempt_id: attemptId, + checkpoint_id: checkpointId, + subgoal_text: String(rec.subgoal_text || (checkpointId === 'final' + ? "Final review against the user's original goal" + : `Subgoal ${checkpointId.slice(0, 8)}`)), + anchor_step_id: rec.anchor_step_id ?? null, + trace_id: rec.trace_id ?? null, + status: 'done', + verdicts: [], + findings: [], + ts: typeof rec.ts === 'number' ? rec.ts : undefined, + session_id: sessionId + }; + byAttempt.set(attemptId, attempt); + } + if (typeof rec.ts === 'number') { + attempt.ts = attempt.ts === undefined ? rec.ts : Math.min(attempt.ts, rec.ts); + } + if (!attempt.trace_id && rec.trace_id) { + attempt.trace_id = rec.trace_id; + } + attempt.verdicts.push({ + item_text: String(rec.item_text || ''), + kind: String(rec.kind || ''), + status: String(rec.status || ''), + evidence: String(rec.evidence || ''), + suggestion: rec.suggestion ? String(rec.suggestion) : '', + when: rec.when ? String(rec.when) : undefined + }); + } + + const logs: any[] = []; + for (const attempt of byAttempt.values()) { + const statuses = new Set(attempt.verdicts.map((v: any) => v.status)); + if (statuses.size === 1) { + const only = [...statuses][0]; + if (only === 'superseded' || only === 'unchecked') attempt.status = only; + } + const ts = attempt.ts ?? Date.now() / 1000; + attempt.timestamp = ts; + logs.push({ + type: 'checker_event', + session_id: sessionId, + timestamp: new Date(ts * 1000).toISOString(), + checks_snapshot: true, + data: attempt + }); + } + if (runOutcome && typeof runOutcome === 'object') { + const lastTs = logs.length > 0 + ? Math.max(...logs.map((l) => new Date(l.timestamp).getTime() / 1000)) + : Date.now() / 1000; + const ts = lastTs + 0.001; + logs.push({ + type: 'checker_event', + session_id: sessionId, + timestamp: new Date(ts * 1000).toISOString(), + checks_snapshot: true, + data: { event: 'run_outcome', phase: 'outcome', ...runOutcome, ts, timestamp: ts, session_id: sessionId } + }); + } + return logs; + } + public fetchNotes(sessionId: string): void { if (!sessionId) { this.currentNotes.set({}); diff --git a/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts b/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts index ed5a985..4e7e3e7 100644 --- a/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +++ b/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts @@ -91,3 +91,261 @@ describe('stream aggregator timeline ordering', () => { .toEqual(['text', 'tool']); }); }); + +describe('stream aggregator checker lane', () => { + const started = { + type: 'checker_event', + timestamp: '2026-08-25T20:00:05.000Z', + data: { + event: 'attempt_started', + phase: 'checkpoint', + attempt_id: 'abc#1', + checkpoint_id: 'abc', + subgoal_text: 'Create the alarm', + trace_id: 'trace-checker-1', + items: [{ kind: 'verify', text: 'alarm exists' }], + ts: 1756152005 + } + }; + + it('creates a running checker block from attempt_started and finishes it by attempt id', () => { + const blocks = consolidateLogsToBlocks([ + started, + { + type: 'checker_event', + timestamp: '2026-08-25T20:00:09.000Z', + data: { + event: 'attempt_finished', + attempt_id: 'abc#1', + status: 'done', + verdicts: [{ item_text: 'alarm exists', kind: 'verify', status: 'passed', evidence: 'seen' }], + findings: [], + ts: 1756152009 + } + } + ]); + + expect(blocks.length).toBe(1); + expect(blocks[0].type).toBe('checker'); + expect(blocks[0].id).toBe('checker-abc#1'); + expect(blocks[0].data.isCompleted).toBeTrue(); + expect(blocks[0].data.status).toBe('done'); + expect(blocks[0].data.trace_id).toBe('trace-checker-1'); + expect(blocks[0].data.verdicts.length).toBe(1); + expect(blocks[0].data.duration).toBe(4); + }); + + it("routes the checker's stream and tool traces by parent trace id, not by the current step", () => { + const blocks = consolidateLogsToBlocks([ + { + type: 'step_recorded', + timestamp: '2026-08-25T20:00:01.000Z', + data: { step_id: 'step-1', step_number: 1, timestamp: 1756152001 } + }, + started, + { + type: 'llm_stream', + timestamp: '2026-08-25T20:00:06.000Z', + data: { + execution_id: 'exec-checker', + step_id: 'step-1', + parent_trace_id: 'trace-checker-1', + stream_type: 'thinking', + text: 'Inspecting the alarm list', + isCompleted: false + } + }, + { + type: 'trace_recorded', + timestamp: '2026-08-25T20:00:07.000Z', + data: { + trace_id: 'tool-probe', + parent_trace_id: 'trace-checker-1', + agent_name: 'checker', + type: 'tool', + name: 'probe_device', + step_id: 'step-1', + status: 'success' + } + } + ]); + + const step = blocks.find((b) => b.id === 'step-step-1')!; + const checker = blocks.find((b) => b.id === 'checker-abc#1')!; + expect(step.data.operator_native_thinking).toBeUndefined(); + expect(step.data.generic_tools || []).toEqual([]); + expect(checker.data.operator_native_thinking).toBe('Inspecting the alarm list'); + expect(checker.data.generic_tools.map((t: any) => t.trace_id)).toEqual(['tool-probe']); + expect(checker.data.step_id).toBeUndefined(); + expect(blocks.map((b) => b.id)).toEqual(['step-step-1', 'checker-abc#1']); + }); + + it('keeps one stream segment per checker turn so text and tool calls interleave by time', () => { + const turn = (execId: string, timestamp: string, text: string) => ({ + type: 'llm_stream', + timestamp, + data: { + execution_id: execId, + parent_trace_id: 'trace-checker-1', + stream_type: 'text', + text, + isCompleted: false + } + }); + const blocks = consolidateLogsToBlocks([ + started, + turn('exec-1', '2026-08-25T20:00:06.000Z', 'Let me look at step 2.'), + { + type: 'trace_recorded', + timestamp: '2026-08-25T20:00:07.000Z', + data: { + trace_id: 'tool-detail', + parent_trace_id: 'trace-checker-1', + agent_name: 'checker', + type: 'tool', + name: 'get_step_detail', + status: 'success', + timestamp: '2026-08-25T20:00:07.000Z' + } + }, + turn('exec-2', '2026-08-25T20:00:08.000Z', 'The alarm is there.'), + turn('exec-1', '2026-08-25T20:00:08.500Z', 'Let me look at step 2. Done.') + ]); + + const checker = blocks.find((b) => b.id === 'checker-abc#1')!; + expect(checker.data.stream_segments.map((s: any) => s.execution_id)).toEqual(['exec-1', 'exec-2']); + // A later chunk of an earlier turn keeps that turn's first-seen time. + expect(checker.data.stream_segments[0].text).toBe('Let me look at step 2. Done.'); + expect(checker.data.stream_segments[0].timestamp).toBe('2026-08-25T20:00:06.000Z'); + const events = getSortedStepEvents(checker.data); + expect(events.map((e) => e.type)).toEqual(['text', 'tool', 'text']); + expect(events[0].data.text).toBe('Let me look at step 2. Done.'); + expect(events[2].data.text).toBe('The alarm is there.'); + }); + + it('builds the run outcome block once and keeps it idempotent', () => { + const outcome = { + type: 'checker_event', + timestamp: '2026-08-25T20:00:20.000Z', + data: { event: 'run_outcome', task_status: 'completed', tests: { passed: 1, failed: 0, inconclusive: 0, unchecked: 0 } } + }; + const blocks = consolidateLogsToBlocks([outcome, outcome]); + expect(blocks.length).toBe(1); + expect(blocks[0].id).toBe('checker-outcome'); + expect(blocks[0].data.phase).toBe('outcome'); + expect(blocks[0].data.task_status).toBe('completed'); + }); +}); + +describe('stream aggregator step ownership', () => { + const plannerStream = { + type: 'llm_stream', + timestamp: '2026-09-02T03:57:25.000Z', + data: { execution_id: 'exec-planner', stream_type: 'text', text: 'I have formulated the task plan.', isCompleted: true } + }; + const tap = { trace_id: 'trace-tap-3', type: 'action', name: 'tap', timestamp: 1788374289.37, payload: { args: { action: 'tap' } } }; + + it('does not fold a step without streamed text into an earlier untagged stream block', () => { + const blocks = consolidateLogsToBlocks([ + plannerStream, + { + type: 'step_recorded', + timestamp: '2026-09-02T03:57:47.000Z', + data: { step_id: 's1', step_number: 1, operator_raw_thinking: 'Launching Maps', generic_tools: [] } + }, + { + type: 'step_recorded', + timestamp: '2026-09-02T03:58:09.000Z', + data: { step_id: 's3', step_number: 3, generic_tools: [tap] } + } + ]); + + expect(blocks.map((b) => b.id)).toEqual(['stream-exec-planner', 'step-s1', 'step-s3']); + expect(blocks[0].type).toBe('llm_stream'); + expect(blocks[0].data.generic_tools).toEqual([]); + expect(blocks[0].data.operator_raw_thinking).toBe('I have formulated the task plan.'); + expect(blocks[2].data.generic_tools.map((t: any) => t.trace_id)).toEqual(['trace-tap-3']); + }); + + it('creates the step block for a tagged trace that arrives before its step is recorded', () => { + const blocks = consolidateLogsToBlocks([ + { + type: 'step_recorded', + timestamp: '2026-09-02T03:58:03.000Z', + data: { step_id: 's2', step_number: 2, operator_raw_thinking: 'Dismissing the dialog', generic_tools: [] } + }, + { + type: 'trace_recorded', + timestamp: '2026-09-02T03:58:09.300Z', + data: { ...tap, step_id: 's3', status: 'running' } + }, + { + type: 'step_recorded', + timestamp: '2026-09-02T03:58:09.400Z', + data: { step_id: 's3', step_number: 3, generic_tools: [{ ...tap, status: 'success' }] } + } + ]); + + expect(blocks.map((b) => b.id)).toEqual(['step-s2', 'step-s3']); + expect(blocks[0].data.generic_tools).toEqual([]); + expect(blocks[1].data.generic_tools.length).toBe(1); + expect(blocks[1].data.generic_tools[0].status).toBe('success'); + }); + + it('still attaches untagged traces to the latest preceding block', () => { + const blocks = consolidateLogsToBlocks([ + plannerStream, + { + type: 'trace_recorded', + timestamp: '2026-09-02T03:57:26.000Z', + data: { trace_id: 'trace-note', type: 'tool', name: 'save_note', payload: { args: {} } } + } + ]); + + expect(blocks.length).toBe(1); + expect(blocks[0].data.generic_tools.map((t: any) => t.trace_id)).toEqual(['trace-note']); + }); +}); + +describe('stream aggregator checker history relocation', () => { + it('moves persisted checker tool traces from the operator step to the attempt block', () => { + const blocks = consolidateLogsToBlocks([ + { + type: 'step_updated', + timestamp: '2026-08-25T20:00:01.000Z', + history_snapshot: true, + data: { + step_id: 'step-1', + step_number: 1, + timestamp: 1756152001, + generic_tools: [ + { trace_id: 'trace-checker-1', type: 'agent', name: 'checker', status: 'success' }, + { trace_id: 'tool-probe', parent_trace_id: 'trace-checker-1', type: 'tool', name: 'probe_device', status: 'success' }, + { trace_id: 'tool-note', parent_trace_id: 'operator-trace', type: 'tool', name: 'save_note', status: 'success' } + ] + } + }, + { + type: 'checker_event', + timestamp: '2026-08-25T20:00:09.000Z', + checks_snapshot: true, + data: { + event: 'attempt_finished', + phase: 'checkpoint', + attempt_id: 'abc#1', + checkpoint_id: 'abc', + subgoal_text: 'Create the alarm', + trace_id: 'trace-checker-1', + status: 'done', + verdicts: [{ item_text: 'alarm exists', kind: 'verify', status: 'passed', evidence: 'seen' }], + ts: 1756152009 + } + } + ]); + + const step = blocks.find((b) => b.id === 'step-step-1')!; + const checker = blocks.find((b) => b.id === 'checker-abc#1')!; + expect(step.data.generic_tools.map((t: any) => t.trace_id)).toEqual(['tool-note']); + expect(checker.data.generic_tools.map((t: any) => t.trace_id)).toEqual(['tool-probe']); + }); +}); diff --git a/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts b/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts index f4591e9..ad17c9d 100644 --- a/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +++ b/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { StepBlock, PhaseBlock, StepEvent } from '../core/models/stream.model'; +import { StepBlock, PhaseBlock, StepEvent, StreamSegment } from '../core/models/stream.model'; import { isAndroidAction, isReportStatusAction, getReportStatusExplanation, getReportStatusValue } from './action-formatter.util'; import { getUniqueGenericTools } from './tool-formatter.util'; @@ -32,6 +32,145 @@ export function getItemTimestamp(ts: any, fallback: number = 0): number { return fallback; } +/** + * Index of the `checker` block that owns a trace id (the Checker's own agent + * trace). The Checker runs concurrently with Operator steps, so its streamed + * reasoning and tool traces are routed by `parent_trace_id`, never by the + * step that happens to be current. + */ +function findCheckerBlockByTrace(blocks: StepBlock[], parentTraceId: any): number { + if (!parentTraceId) return -1; + const key = String(parentTraceId); + return blocks.findIndex(b => b.type === 'checker' && b.data?.trace_id && String(b.data.trace_id) === key); +} + +/** Latest still-running checker block (fallback when only the agent name is known). */ +function findRunningCheckerBlock(blocks: StepBlock[]): number { + for (let i = blocks.length - 1; i >= 0; i--) { + if (blocks[i].type === 'checker' && blocks[i].data?.isCompleted === false) return i; + } + return -1; +} + +/** + * Apply one `checker_event` (attempt_started / attempt_finished / run_outcome) + * to the block list. Attempts are keyed by attempt id so live events and the + * ledger backfill merge instead of duplicating. + */ +function applyCheckerLog(blocks: StepBlock[], log: any): void { + const d = log.data || {}; + if (!d.event) return; + + if (d.event === 'run_outcome') { + const id = 'checker-outcome'; + const idx = blocks.findIndex(b => b.id === id); + const data = { + ...(idx > -1 ? blocks[idx].data : {}), + ...d, + phase: 'outcome', + status: 'done', + isCompleted: true, + generic_tools: [] + }; + const block: StepBlock = { id, type: 'checker', timestamp: log.timestamp, data }; + if (idx > -1) blocks[idx] = block; else blocks.push(block); + return; + } + + if (!d.attempt_id) return; + const id = `checker-${d.attempt_id}`; + const idx = blocks.findIndex(b => b.id === id); + const existing = idx > -1 ? blocks[idx] : null; + const ts = typeof d.ts === 'number' ? d.ts : (log.timestamp ? new Date(log.timestamp).getTime() / 1000 : undefined); + + if (d.event === 'attempt_started') { + const data = { + ...(existing?.data || {}), + ...d, + // A late "started" must not resurrect a finished attempt. + status: existing?.data?.isCompleted ? existing.data.status : 'running', + isCompleted: existing?.data?.isCompleted ?? false, + started_at: existing?.data?.started_at ?? ts, + verdicts: existing?.data?.verdicts || [], + findings: existing?.data?.findings || [], + generic_tools: existing?.data?.generic_tools || [] + }; + const block: StepBlock = { id, type: 'checker', timestamp: existing?.timestamp || log.timestamp, data }; + if (existing) blocks[idx] = block; else blocks.push(block); + return; + } + + if (d.event === 'attempt_finished') { + const startedAt = existing?.data?.started_at ?? ts; + const verdicts = Array.isArray(d.verdicts) ? d.verdicts : (existing?.data?.verdicts || []); + const items = existing?.data?.items?.length + ? existing.data.items + : verdicts.map((v: any) => ({ kind: v.kind, text: v.item_text, when: v.when })); + const data = { + ...(existing?.data || {}), + ...d, + status: d.status || 'done', + verdicts, + items, + findings: Array.isArray(d.findings) ? d.findings : (existing?.data?.findings || []), + generic_tools: existing?.data?.generic_tools || [], + started_at: startedAt, + finished_at: ts, + duration: startedAt !== undefined && ts !== undefined ? Math.max(0, ts - startedAt) : undefined, + isCompleted: true + }; + const block: StepBlock = { id, type: 'checker', timestamp: existing?.timestamp || log.timestamp, data }; + if (existing) blocks[idx] = block; else blocks.push(block); + } +} + +/** + * Persisted steps list the Checker's tool traces (and its agent trace) under + * the Operator step that was current while it ran, because traces are stored + * by step id. Once the checker blocks exist (ledger backfill carries the + * attempt's trace id), move those traces to the attempt they belong to and + * drop the agent trace itself. + */ +function relocateCheckerTools(blocks: StepBlock[]): void { + const checkerByTrace = new Map(); + blocks.forEach((b, i) => { + if (b.type === 'checker' && b.data?.trace_id) { + checkerByTrace.set(String(b.data.trace_id), i); + } + }); + if (checkerByTrace.size === 0) return; + + blocks.forEach((block, i) => { + if (block.type === 'checker') return; + const tools: any[] = block.data?.generic_tools || []; + if (tools.length === 0) return; + const keep: any[] = []; + let moved = false; + for (const tool of tools) { + const ownTrace = tool?.trace_id ? String(tool.trace_id) : ''; + const parent = tool?.parent_trace_id ? String(tool.parent_trace_id) : ''; + if (ownTrace && checkerByTrace.has(ownTrace) && tool?.type === 'agent') { + moved = true; // the Checker's own agent trace: not a tool of the step + continue; + } + const target = parent ? checkerByTrace.get(parent) : undefined; + if (target === undefined) { + keep.push(tool); + continue; + } + const checker = blocks[target]; + const existing: any[] = checker.data.generic_tools || []; + if (!existing.some((t) => t.trace_id && t.trace_id === tool.trace_id)) { + blocks[target] = { ...checker, data: { ...checker.data, generic_tools: [...existing, tool] } }; + } + moved = true; + } + if (moved) { + blocks[i] = { ...block, data: { ...block.data, generic_tools: keep } }; + } + }); +} + /** * Consolidate raw SSE logs into deduplicated and ordered StepBlocks */ @@ -43,6 +182,11 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { rawLogs.forEach(log => { if (!log) return; + if (log.type === 'checker_event') { + applyCheckerLog(blocks, log); + return; + } + if (log.type === 'llm_stream') { const execId = log.data?.execution_id || log.id; const stepId = log.data?.step_id; @@ -50,6 +194,52 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { const text = log.data?.text || ''; const isCompleted = log.data?.isCompleted ?? false; + // The Checker's own reasoning streams under its agent trace: route it to + // the attempt block, never to the Operator step that is current. + const checkerIndex = findCheckerBlockByTrace(blocks, log.data?.parent_trace_id); + if (checkerIndex > -1) { + const checkerBlock = blocks[checkerIndex]; + const checkerData: any = { ...checkerBlock.data }; + // The Checker is a multi-turn tool loop: every LLM turn streams under + // its own execution id. Keep one segment per turn (with its first-seen + // time) so the timeline can interleave the text with the tool calls it + // triggered, instead of collapsing all turns into one early block. + const segments: StreamSegment[] = Array.isArray(checkerData.stream_segments) + ? [...checkerData.stream_segments] + : []; + const segmentIndex = segments.findIndex( + (s) => s.execution_id === execId && s.stream_type === streamType + ); + const segment: StreamSegment = { + execution_id: execId, + stream_type: streamType === 'thinking' ? 'thinking' : 'text', + text, + timestamp: segmentIndex > -1 ? segments[segmentIndex].timestamp : log.timestamp, + isCompleted + }; + if (segmentIndex > -1) segments[segmentIndex] = segment; else segments.push(segment); + checkerData.stream_segments = segments; + // Flat fields stay as the joined text for consumers that only know steps. + const joined = (type: string) => segments + .filter((s) => s.stream_type === type && s.text.trim()) + .map((s) => s.text) + .join('\n\n'); + checkerData.operator_native_thinking = joined('thinking'); + checkerData.operator_raw_thinking = joined('text'); + if (streamType === 'thinking') { + checkerData.operator_native_thinking_timestamp = + checkerData.operator_native_thinking_timestamp || log.timestamp; + } else { + checkerData.operator_raw_thinking_timestamp = + checkerData.operator_raw_thinking_timestamp || log.timestamp; + } + if (execId && !checkerData.execution_id) { + checkerData.execution_id = execId; + } + blocks[checkerIndex] = { ...checkerBlock, data: checkerData }; + return; + } + // Find existing block: match by stepId if available, or by execId, or active incomplete block let existingIndex = -1; if (stepId) { @@ -111,15 +301,12 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { // Find if there is an existing block for this step or an unattached stream block from this turn let existingIndex = blocks.findIndex(b => b.id === `step-${stepId}` || b.data?.step_id === stepId); - // If not found by step_id, check if there is an open stream block from the same execution turn - if (existingIndex === -1) { - for (let i = blocks.length - 1; i >= 0; i--) { - if (blocks[i].type === 'llm_stream' && (!blocks[i].data?.step_id || blocks[i].data?.step_id === stepId)) { - existingIndex = i; - break; - } - } - } + // No adoption of untagged stream blocks here: the Operator's streams + // always carry the step id (Perception / the Flash turn pre-allocate it + // before the LLM call), so a stream without one belongs to another agent + // (Planner, Checker...). A step whose Operator emitted only tool calls + // and no text used to steal the latest such block -- typically the + // Planner's -- and render its action inside it at the top of the timeline. if (existingIndex > -1) { const existingBlock = blocks[existingIndex]; @@ -185,8 +372,18 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { let stepId = log.data.step_id; let existingIndex = -1; + // 0. Tools invoked by the Checker belong to its attempt block (routed by + // the parent trace, falling back to the running attempt by agent name). + let checkerIndex = findCheckerBlockByTrace(blocks, log.data.parent_trace_id); + if (checkerIndex === -1 && String(log.data.agent_name || '').toLowerCase() === 'checker') { + checkerIndex = findRunningCheckerBlock(blocks); + } + if (checkerIndex > -1) { + existingIndex = checkerIndex; + } + // 1. If this trace_id already exists in some block, update that block directly - if (log.data.trace_id) { + if (existingIndex === -1 && log.data.trace_id) { existingIndex = blocks.findIndex(b => b.data?.generic_tools?.some((t: any) => t.trace_id === log.data.trace_id) ); @@ -202,8 +399,13 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { existingIndex = blocks.findIndex(b => b.id === 'step-pre-planning' || b.data?.step_id === 'pre-planning'); } - // 4. Otherwise find by timestamp among existing step blocks - if (existingIndex === -1) { + // 4. Untagged traces only: attach to the latest block that precedes + // them. A trace that names a step whose block does not exist yet + // (tool call before any streamed text, or an action recorded before + // the step in Flash) creates that step's block below instead of being + // glued to an earlier step, where the later `step_recorded` (which + // carries the same trace) would leave a duplicate card. + if (existingIndex === -1 && !stepId) { const logTime = log.timestamp ? new Date(log.timestamp).getTime() : 0; let maxStepTime = -1; for (let i = 0; i < blocks.length; i++) { @@ -255,6 +457,8 @@ export function consolidateLogsToBlocks(rawLogs: any[]): StepBlock[] { } }); + relocateCheckerTools(blocks); + blocks.sort((a, b) => { const timeA = a.timestamp ? new Date(a.timestamp).getTime() : 0; const timeB = b.timestamp ? new Date(b.timestamp).getTime() : 0; @@ -470,13 +674,17 @@ export function getSortedStepEvents( const rawText = typeof stepData.operator_raw_thinking === 'string' ? stepData.operator_raw_thinking.trim() : ''; + const segments: StreamSegment[] | null = Array.isArray(stepData.stream_segments) && stepData.stream_segments.length > 0 + ? stepData.stream_segments + : null; const signature = [ toolsLen, actionTs ?? '', stepData.operator_native_thinking_timestamp ?? '', stepData.operator_raw_thinking_timestamp ?? '', nativeText.length, - rawText.length + rawText.length, + segments ? segments.map((s) => `${s.execution_id}:${s.stream_type}:${s.text.length}`).join(',') : '' ].join('|'); if (cache) { @@ -492,22 +700,36 @@ export function getSortedStepEvents( // Text and tools share one timeline. Live streams carry their first-seen // timestamps; persisted steps use the step timestamp as a stable fallback. - if (nativeText) { - events.push({ - type: 'thinking', - data: { text: stepData.operator_native_thinking }, - timestamp: getItemTimestamp(stepData.operator_native_thinking_timestamp, fallbackTime), - sequence: sequence++ + if (segments) { + // Multi-turn agent (Checker): one event per streamed turn, each at the + // time its first chunk arrived, so turns sort between the tool calls. + segments.forEach((segment) => { + if (!segment.text || !segment.text.trim()) return; + events.push({ + type: segment.stream_type === 'thinking' ? 'thinking' : 'text', + data: { text: segment.text, execution_id: segment.execution_id, segment: true }, + timestamp: getItemTimestamp(segment.timestamp, fallbackTime), + sequence: sequence++ + }); }); - } + } else { + if (nativeText) { + events.push({ + type: 'thinking', + data: { text: stepData.operator_native_thinking }, + timestamp: getItemTimestamp(stepData.operator_native_thinking_timestamp, fallbackTime), + sequence: sequence++ + }); + } - if (rawText) { - events.push({ - type: 'text', - data: { text: stepData.operator_raw_thinking }, - timestamp: getItemTimestamp(stepData.operator_raw_thinking_timestamp, fallbackTime), - sequence: sequence++ - }); + if (rawText) { + events.push({ + type: 'text', + data: { text: stepData.operator_raw_thinking }, + timestamp: getItemTimestamp(stepData.operator_raw_thinking_timestamp, fallbackTime), + sequence: sequence++ + }); + } } // Add action_taken if present and valid. diff --git a/apps/showcase_ui/src/app/utils/tool-formatter.util.ts b/apps/showcase_ui/src/app/utils/tool-formatter.util.ts index f8c5c4f..a2246b7 100644 --- a/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +++ b/apps/showcase_ui/src/app/utils/tool-formatter.util.ts @@ -675,10 +675,20 @@ export function getToolDisplayLabel(tool: any, isFirstSaveNote: boolean = false) const q = args.specific_query || args.query || ''; return q ? `Analyzing audio track: "${q}"` : 'Analyzing recording audio track'; } - case 'get_step_details': - return 'Retrieving step execution details'; - case 'get_step_screenshot': - return 'Inspecting screenshot evidence'; + case 'get_step_detail': + case 'get_step_details': { + const n = args.step_no ?? args.step_number ?? args.step; + return n !== undefined && n !== '' ? `Reviewing step ${n}` : 'Reviewing step details'; + } + case 'get_step_screenshot': { + const n = args.step_no ?? args.step_number ?? args.step; + const which = String(args.which || '').toLowerCase() === 'post' ? 'after' : 'before'; + return n !== undefined && n !== '' ? `Looking at the screen ${which} step ${n}` : 'Looking at a step screenshot'; + } + case 'probe_device': { + const kind = args.kind ? String(args.kind).replace(/_/g, ' ') : ''; + return kind ? `Reading ${kind} from the device` : 'Reading device state'; + } case 'search_history_for_text': { const q = args.text || args.query || ''; return q ? `Searching execution trace for "${q}"` : 'Searching execution trace'; @@ -754,10 +764,13 @@ export function getToolIcon(tool: any): string { return 'smart_toy'; case 'analyze_audio_only': return 'graphic_eq'; + case 'get_step_detail': case 'get_step_details': return 'manage_search'; case 'get_step_screenshot': return 'image_search'; + case 'probe_device': + return 'sensors'; case 'search_history_for_text': return 'find_in_page'; case 'outputter': diff --git a/artemis/__init__.py b/artemis/__init__.py index 97aa8ee..2c9df42 100644 --- a/artemis/__init__.py +++ b/artemis/__init__.py @@ -14,7 +14,7 @@ """☕ ARTEMIS: Autonomous Multimodal Android Agent & Testing Framework.""" -__version__ = "0.2.0" +from artemis._version import __version__ from artemis.interfaces.sdk.client import ArtemisClient, ConcurrencyMode from artemis.interfaces.sdk.task import StreamEvent, StreamEventType, Task diff --git a/artemis/_version.py b/artemis/_version.py new file mode 100644 index 0000000..e7e3e02 --- /dev/null +++ b/artemis/_version.py @@ -0,0 +1,30 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Runtime package version sourced exclusively from distribution metadata.""" + +from importlib.metadata import PackageNotFoundError, version + + +def get_version() -> str: + """Return the installed Artemis version, or an honest source-tree sentinel.""" + try: + return version("artemis") + except PackageNotFoundError: + return "0+unknown" + + +__version__ = get_version() + +__all__ = ["__version__", "get_version"] diff --git a/artemis/agents/checker/checker.py b/artemis/agents/checker/checker.py index 16d779b..bb98c04 100644 --- a/artemis/agents/checker/checker.py +++ b/artemis/agents/checker/checker.py @@ -39,10 +39,10 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any, Literal -from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage +from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage from langchain_core.tools import BaseTool, tool -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ValidationError from artemis.constants import CHECKER_MAX_ITERATIONS from artemis.context import ArtemisContext @@ -416,14 +416,23 @@ def _format_check_items(check_items: list) -> str: async def _structured_report(llm, messages) -> CheckReport: structured_llm = llm.with_structured_output(CheckReport) + # A conversation must end with a user turn: Gemini rejects requests whose + # last message is the model's own answer ("Requests ending with a model + # turn are not supported"), which is exactly the state after the loop's + # final tool-free reply. Nudge with an explicit verdict request instead. + if messages and isinstance(messages[-1], AIMessage): + messages = [ + *messages, + HumanMessage(content="Now provide your structured verdict report."), + ] result = await invoke_llm_with_timeout_message(structured_llm.ainvoke(messages)) if isinstance(result, CheckReport): return result if isinstance(result, dict): try: return CheckReport.model_validate(result) - except Exception: - pass + except ValidationError as exc: + logger.debug("Structured verdict dict failed CheckReport validation: %s", exc) return CheckReport(verdicts=[]) @@ -502,6 +511,17 @@ async def _run_check_loop( shot = _load_step_screenshot( ctx, int(args.get("step_no", -1)), str(args.get("which", "pre")) ) + # The image is spliced in below; invoking the (traced) tool as + # well records the lookup as a tool trace so the timeline shows + # it in order with the rest of the check. + screenshot_tool = next((t for t in traced_tools if t.name == tool_name), None) + if screenshot_tool is not None: + try: + await invoke_tool_with_injection( + tool=screenshot_tool, args=args, tool_call_id=tc["id"] + ) + except Exception as e: + logger.debug(f"Screenshot lookup trace not recorded: {e}") messages.append(ToolMessage(tool_call_id=tc["id"], content=shot.description)) if shot.image_b64: messages.append( @@ -547,6 +567,16 @@ async def _run_check_loop( # --- Entry points -------------------------------------------------------------------- +def _announce(ctx: ArtemisContext, **kwargs) -> None: + """Lazy bridge to the checkpoint bus (avoids an import cycle at module load).""" + try: + from artemis.graph.checkpoints import announce_checker_attempt + + announce_checker_attempt(ctx, **kwargs) + except Exception as e: + logger.debug(f"Checker attempt announcement skipped: {e}") + + @trace(type="agent", name="checker") async def run_checkpoint_check( ctx: ArtemisContext, @@ -554,6 +584,7 @@ async def run_checkpoint_check( anchor, goal: str, subgoal_text: str, + attempt_id: str | None = None, ) -> CheckReport: """Audits one completed subgoal's on_complete check items. @@ -563,6 +594,15 @@ async def run_checkpoint_check( """ items = list(check_items) logger.info(f"Starting checkpoint check for '{subgoal_text}' ({len(items)} item(s))") + _announce( + ctx, + attempt_id=attempt_id, + phase="checkpoint", + checkpoint_id=attempt_id.split("#", 1)[0] if attempt_id else "", + subgoal_text=subgoal_text, + anchor_step_id=getattr(anchor, "anchor_step_id", None), + check_items=items, + ) prompts = _load_prompts() tools = build_checker_tools(ctx, "checkpoint") probe_registered = any(t.name == "probe_device" for t in tools) @@ -655,11 +695,21 @@ async def run_final_check( plan_text: str, ledger: list[dict], check_items, + attempt_id: str | None = None, ) -> CheckReport: """Final review at task exit: audits the user's original goal and all declared check items, with the final screen state available.""" items = list(check_items) logger.info(f"Starting final check ({len(items)} declared item(s))") + _announce( + ctx, + attempt_id=attempt_id, + phase="final", + checkpoint_id="final", + subgoal_text="Final review against the user's original goal", + anchor_step_id=None, + check_items=items, + ) prompts = _load_prompts() tools = build_checker_tools(ctx, "final") probe_registered = any(t.name == "probe_device" for t in tools) diff --git a/artemis/agents/explorer/_facade.py b/artemis/agents/explorer/_facade.py index 21518f6..8e8063d 100644 --- a/artemis/agents/explorer/_facade.py +++ b/artemis/agents/explorer/_facade.py @@ -17,7 +17,7 @@ The Explorer implementation is split across several modules, but external callers (and ``unittest.mock.patch`` targets in the test suite) address collaborators such as ``settings``, ``StorageManager``, ``draw_dots``, -``search_ui_func``, ``get_llm`` etc. through the historical module path +``get_llm`` etc. through the historical module path ``artemis.agents.explorer.explorer``. Split-out code therefore resolves those names at call time via :func:`facade` instead of importing them directly, so patches applied to the facade module keep affecting the split-out code. diff --git a/artemis/agents/explorer/constants.py b/artemis/agents/explorer/constants.py index bc4f3e8..a8ae1e7 100644 --- a/artemis/agents/explorer/constants.py +++ b/artemis/agents/explorer/constants.py @@ -12,61 +12,38 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Constants and prompt templates for the UI Explorer agent.""" +"""The ``ask_explorer`` tool contract as seen by calling agents. -EXPLORE_DESCRIPTIONS = { - "flash": { - "description": ( - "[EXPLORER] Call this to ask the UI Explorer agent to " - "locate coordinates on the screen layout by object detection." - ), - "query_description": ( - "Pipe-separated list of target element descriptions, semantic" - " action terms, or color descriptors to search for concurrently" - " (e.g., 'red settings button | gear icon')." - ), - "rule_prompt": ( - "Use the explorer tool to quickly check for visual icons, buttons, or text by name." - ), - }, - "pro": { - "description": ( - "[EXPLORER] Call this to ask the UI Explorer agent to locate" - " coordinates on the screen layout by executing UI search," - " coordinate search, and object detection." - ), - "query_description": ( - "The target element or information to search for, including descriptions." - ), - "rule_prompt": ( - "Call the explorer tool to search or inspect it (maximum {max_iterations} tries)." - ), - "version_prompt": ( - "You are running with a maximum of {max_iterations} turns: on your" - " final turn, you will receive a warning reminding you to call" - " submit_answer." - ), - }, - "ultra": { - "description": ( - "[EXPLORER] Full visual reasoning agent. Deeply inspects screen" - " regions and utilizes all perception/pixel-level tools with a high" - " execution limit (up to {max_iterations} turns) for" - " high-difficulty or layout-critical tasks." - ), - "query_description": ( - "The target element or information to search for, including descriptions." - ), - "rule_prompt": ( - "For highly complex visual searches, details validation, or layout" - " verification, invoke the explorer tool to perform multi-turn" - " visual reasoning and detailed coordinate checks (maximum" - " {max_iterations} tries)." - ), - "version_prompt": ( - "You are running with a maximum of {max_iterations} turns: on your" - " final turn, you will receive a warning reminding you to call" - " submit_answer." - ), - }, -} +The contract is deliberately tier-agnostic: the Operator, Validator and Flash +runner receive the same tool name, description and argument schema whichever +Explorer tier the user configured. Tier behavior lives in +:mod:`artemis.agents.explorer.tiers`. +""" + +ASK_EXPLORER_TOOL_NAME = "ask_explorer" + +ASK_EXPLORER_DESCRIPTION = ( + "[EXPLORER] Ask the UI Explorer to locate elements on the current screen" + " that are missing from the indexed element list or whose listed" + " coordinates look wrong. Every element it finds is appended to the" + " indexed list with a new index and returned with its normalized [x, y]" + " coordinate, so you can act on it right away." +) + +ASK_EXPLORER_QUERY_DESCRIPTION = ( + "What to find, described the way you see it on the screenshot: visible" + " text, icon shape, color, position, or nearby landmarks (e.g. 'gear icon" + " top-right', 'blue Send button below the message box'). Several targets" + " may be listed separated by ' | '." +) + +ASK_EXPLORER_CONTEXT_FEEDBACK_DESCRIPTION = ( + "Optional. What went wrong with earlier attempts or extra hints, e.g." + " which returned candidates were wrong and why." +) + +#: Fed to the Explorer's own system prompt for loop engines. +EXECUTION_CONSTRAINT_TEMPLATE = ( + "You are running with a maximum of {max_turns} turns: on your final turn" + " you will receive a warning and only `submit_answer` will be available." +) diff --git a/artemis/agents/explorer/explorer.json b/artemis/agents/explorer/explorer.json index 914e3d5..373fee8 100644 --- a/artemis/agents/explorer/explorer.json +++ b/artemis/agents/explorer/explorer.json @@ -1,27 +1,57 @@ +// Explorer system prompt. Each section renders as "# SECTION" followed by +// "- bullet" lines. A bullet is either a plain string or an object +// {"text": "...", "requires": ["tool_a", ...]} that is emitted only when +// every listed tool is exposed for the active tier (see tiers.py). +// Tools by tier: pro = ask_perception_tool; ultra = ask_perception_tool, +// detect_objects, get_ocr_list, inspect_region, ask_image_processor. +// submit_answer is always available. { - "IDENTITY": "You are the agentic UI Explorer designed to identify objects, scan text, and extract UI attributes on a phone screen.", + "IDENTITY": "You are the agentic UI Explorer: given a phone screenshot and a query, you locate the matching UI element(s) and return their normalized [nx, ny] coordinates on a 0-1000 scale.", "OPERATING PRINCIPLES": [ - "**Confidence-Driven Cost-Benefit Strategy**: Maximize localization accuracy while minimizing execution latency and tool calls:", - " - **Direct Submit (High Confidence)**: If you are highly confident in the target coordinates from the initial annotated screenshot and minimal list, call `submit_answer` immediately in Turn 1 to save time.", - " - **Cost-Benefit Turn Planning (Low Confidence / Ambiguity)**: If you are not highly confident. Consider structuring your exploration turns by weighing the latency/cost of each tool against the expected accuracy payoff:", - " - **Parallel Context Gathering**: MUST parallel calling all perception tools in the first turn to gather broad screen context.", - " - **Targeted Verification**: If micro-details or coordinates are still ambiguous, zoom in on promising candidate areas (parallel tool calls). Note that a single zoom-in or multi-zoom-in operation takes one turn, plan your verification steps accordingly.", - " - **Finalization**: Submit coordinates using `submit_answer`. Stop exploring as soon as further tool calls would yield negligible accuracy gains relative to their latency.", - "**Tool Capabilities**: You have three classes of tools: `Perception` tools (to extract and locate elements, coordinates, or inspect specific regions), `Transformation` tools (for pixel-level image editing), and the `Finalization` tool (to submit final answers and terminate). Note that in your final turn, all other tools are stripped away, leaving only the finalization tool, forcing you to submit your final answer.", - "**Parallel vs Sequential Invocations**: You are fully supported to call perception tools in parallel (within a single turn) to expedite your search. However, transformation tools are expected to be called sequentially (since they transform the image for subsequent perception steps).", - "**Multimodal Perception**: You have access to images marked with bounding boxes and labels. Use both the visual information in the images and the structured text lists to make decisions.", - "**Image Pool Tracking**: Images are managed in an Image Pool (starting with 'img_0'). Transformations via transformation tools will register new image IDs (e.g., 'img_1').", - "**Prefix System**: Elements in the screenshots are labeled with prefixes returned by your tools (e.g., Numbers for initial UI, letters for tool outputs). Always reference these precise labels (e.g., \"I choose S2\") when making decisions.", - "**Contextual Awareness**: Always prioritize checking the context feedback to learn from previous mistakes.", - "**Ask Perception Tool**: Call the unified `ask_perception_tool` specifying `search_query`, `nx`, `ny`, and `detect_queries` to perform text lookups, coordinate queries, and visual detection concurrently.", - "**Locating Components**: Locate target components on screen by matching the X-labels (from text/coordinate searches) and O-labels (from object detection) in the text response with their corresponding green, blue, or red indicator dots on the output images." + "**Confidence-Driven Cost-Benefit Strategy**: Maximize localization accuracy while minimizing turns and tool calls. Every turn costs latency; stop exploring as soon as further calls would yield negligible accuracy gains.", + "**Direct Submit (High Confidence)**: If the initial annotated screenshot and the marked UI element list already make the target coordinates obvious, call `submit_answer` immediately in the first turn.", + { + "text": "**Parallel Context Gathering (Low Confidence / Ambiguity)**: If you are not confident, call `ask_perception_tool` once in the first turn with `search_query` (the visible text or name of the target), `nx`/`ny` (the most plausible spot for the target, to audit which element actually sits there) and `detect_queries` (short visual descriptions of the target). It runs the UI-tree text search, the coordinate audit and visual object detection concurrently and returns all three results with annotated screenshots. Reconcile the results, then call `submit_answer`.", + "requires": ["ask_perception_tool"] + }, + { + "text": "**Targeted Verification**: If micro-details or exact coordinates are still ambiguous after context gathering, zoom in on the promising candidate areas with `inspect_region` (several regions may be inspected in parallel within one turn). Each zoom-in turn costs one of your turns, so plan verification steps accordingly.", + "requires": ["inspect_region"] + }, + { + "text": "**Focused Detection**: Use `detect_objects` on its own when you only need visual detection (for example after a transformation, on a derived image ID), instead of re-running the full `ask_perception_tool` bundle.", + "requires": ["detect_objects"] + }, + { + "text": "**Text Inventory**: Use `get_ocr_list` when the target is text that the UI tree does not expose (canvas-rendered or image text); it lists every OCR-detected text with coordinates.", + "requires": ["get_ocr_list"] + }, + "**Finalization**: Submit coordinates using `submit_answer`. In your final turn all other tools are stripped away, leaving only `submit_answer`, so never postpone the submission past the point where you are reasonably confident.", + "**Tool Classes**: `Perception` tools extract and locate elements, coordinates and text on the screen; the `Finalization` tool (`submit_answer`) submits your ranked candidates and terminates the search.", + { + "text": "**Transformation Tool**: `ask_image_processor` performs pixel-level image editing (cropping, upscaling, masking, contrast changes) and registers the resulting images in the Image Pool. It is slow and expensive; use it only when perception on the original screenshot is insufficient, and call it sequentially because later perception steps depend on its output.", + "requires": ["ask_image_processor"] + }, + { + "text": "**Parallel vs Sequential Invocations**: Perception tools may be called in parallel within a single turn to expedite the search; transformation tools must be called sequentially.", + "requires": ["inspect_region", "ask_image_processor"] + }, + "**Multimodal Perception**: Tool results come as text lists plus annotated screenshots with labeled dots. Use both the visual evidence and the structured text to decide.", + { + "text": "**Image Pool Tracking**: Images are managed in an Image Pool starting with `img_0` (the original screenshot). Transformations register new IDs (`img_1`, `img_2`, ...) that can be passed as `target_image_id` to `detect_objects`. Coordinates returned for derived images are mapped back to the original screen automatically.", + "requires": ["ask_image_processor", "detect_objects"] + }, + "**Label Prefixes**: Plain numbers label the elements of the initial marked UI list. Labels starting with `X` are UI-tree matches from the text search (green dots) and the coordinate audit (blue dot). Labels starting with `O` are OCR text matches (green dots). Labels starting with `D` are visually detected objects (red dots). Always reference these exact labels when reasoning (e.g. \"I choose X2\").", + "**Label Inheritance**: A candidate you submit under a plain-number, `X` or `O` label inherits that element's exact bounds, which lets the caller verify the tap target. When a detection point (`D`) and such a label describe the same element, submit the `X`/`O`/number label rather than the raw detection point.", + "**Locating Components**: Match each label in a tool's text response with its dot of the corresponding color on the returned annotated screenshot to confirm where the element really is before trusting its coordinates.", + "**Contextual Awareness**: Always check the context feedback first to learn from previous mistakes (for example candidates that were already rejected)." ], "BEHAVIORAL GUIDELINES & SOFT CONSTRAINTS": [ - "**Avoid Hallucination**: Avoid guessing coordinates. If you are unable to locate the target element on the screen after trying all perception options, submit `[]` in `candidates` and document your observations as specified in the `submit_answer` schema.", - "**Ambiguity Resolution**: If the screen contains multiple identical elements or several candidates that match the query (e.g., three \"Add\" buttons), you are encouraged to return a ranked list of all viable candidates (up to 10 candidates) via `submit_answer`, ordered by your confidence, with brief descriptions to distinguish them.", - "**Semantic & Icon Synonym Fallback**: Literal exact matches are ideal, but semantic equivalents and synonyms are fully acceptable. If a text element is requested but only a corresponding icon is available, you should attempt to map the synonym and locate the icon (e.g., map \"Search\" to a magnifying glass icon, map \"Add\" to \"+\" symbol)." + "**Avoid Hallucination**: Never guess coordinates. If the target cannot be located after using the available perception options, submit an empty `candidates` list and document your observations in `fallback_message`.", + "**Ambiguity Resolution**: If several elements match the query (e.g. three \"Add\" buttons), return a ranked list of all viable candidates (up to 10) via `submit_answer`, ordered by confidence, with brief descriptions that distinguish them.", + "**Semantic & Icon Synonym Fallback**: Literal matches are ideal, but semantic equivalents and synonyms are acceptable. If text is requested but only a corresponding icon exists, map the synonym and locate the icon (e.g. \"Search\" to a magnifying glass, \"Add\" to a \"+\" symbol)." ], "STRICT CONSTRAINTS": [ - "**Validation Standards**: All submitted coordinates `[nx, ny]` for candidates in `submit_answer` must strictly be integers in the `[0-1000]` range inclusive. If your parameters fail business validation rules, you will receive dynamic error responses in your execution loop, allowing you to self-correct and re-submit." + "**Validation Standards**: Every submitted `coords` value must be `[nx, ny]` with both integers in the inclusive `[0-1000]` range. If a submission fails validation you receive the errors as a tool response so you can correct and re-submit." ] -} \ No newline at end of file +} diff --git a/artemis/agents/explorer/explorer.py b/artemis/agents/explorer/explorer.py index 1c1a552..f0f1ef8 100644 --- a/artemis/agents/explorer/explorer.py +++ b/artemis/agents/explorer/explorer.py @@ -17,6 +17,7 @@ This module is the stable entry point (and ``mock.patch`` target namespace) for the Explorer agent. The implementation is split across sibling modules: +- ``tiers``: the flash / pro / ultra tier table (single source of truth). - ``tool_declarations``: static tool schemas (universal + native). - ``perception_tools``: the ``exec_*`` perception/vision tool method group. - ``universal_runner``: the LangChain-based ``_run_universal`` loop. @@ -24,42 +25,41 @@ for the Explorer agent. The implementation is split across sibling modules: - ``native_runner``: the native Gemini reasoning loop of ``run``. All historical module-level names (``settings``, ``StorageManager``, -``genai``, ``search_ui_func``, ``search_by_coordinates_func``, -``_run_object_detection``, ``draw_dots``, ``perform_ocr``, +``genai``, ``_run_object_detection``, ``draw_dots``, ``perform_ocr``, ``is_ocr_configured``, ``get_llm``, ``UNIVERSAL_EXPLORER_TOOLS``, ...) remain importable and patchable here; split-out code resolves them through this module at call time (see ``artemis.agents.explorer._facade``). """ import asyncio -import base64 -import glob -import hashlib import json import os -from pathlib import Path -import re -import time from typing import Any, Literal -import cv2 from google import genai from google.genai import types -import httpx -from langchain_core.messages import ( - BaseMessage, - HumanMessage, - SystemMessage, - ToolMessage, -) -from artemis.agents.image_processor.image_processor import ImageProcessor +from artemis.agents.explorer.geometry import ( + FALLBACK_SCREEN_SIZE, + is_valid_norm_point, + norm_to_pixel, + pixel_to_norm, +) +from artemis.agents.explorer.native_runner import NativeRunnerMixin +from artemis.agents.explorer.perception_tools import PerceptionToolsMixin +from artemis.agents.explorer.run_setup import RunSetupMixin +from artemis.agents.explorer.screen_index import normalize_text, ScreenElement, ScreenIndex +from artemis.agents.explorer.tiers import ExplorerTier, get_tier +from artemis.agents.explorer.tool_declarations import ( + NATIVE_EXPLORER_TOOL_DECLARATIONS, + UNIVERSAL_EXPLORER_TOOLS, +) +from artemis.agents.explorer.universal_runner import UniversalRunnerMixin from artemis.agents.object_detector.object_detector import _run_object_detection from artemis.config import settings -from artemis.constants import SAFETY_SETTINGS_BLOCK_NONE from artemis.context import ArtemisContext from artemis.data_engine.storage import StorageManager -from artemis.data_engine.trace import TraceSpan, trace +from artemis.data_engine.trace import trace from artemis.graph.state import State from artemis.llm.reliability import ( LLMExhaustedError, @@ -68,28 +68,41 @@ from artemis.llm.reliability import ( retry_policy_for, ) from artemis.services.llm import _record_llm_event, _record_llm_retry, get_llm - -# Import diagnostic functions directly -from artemis.agents.explorer.constants import EXPLORE_DESCRIPTIONS -from artemis.agents.explorer.native_runner import NativeRunnerMixin -from artemis.agents.explorer.perception_tools import PerceptionToolsMixin -from artemis.agents.explorer.run_setup import RunSetupMixin -from artemis.agents.explorer.tool_declarations import ( - NATIVE_EXPLORER_TOOL_DECLARATIONS, - UNIVERSAL_EXPLORER_TOOLS, -) -from artemis.agents.explorer.universal_runner import UniversalRunnerMixin -from artemis.mcp.xml_search_server import ( - search_by_coordinates as search_by_coordinates_func, - search_ui as search_ui_func, -) from artemis.utils.logger import get_logger from artemis.utils.ocr_api import is_ocr_configured, perform_ocr -from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml -from artemis.utils.visualization import draw_dots, format_minimal_list_with_points +from artemis.utils.visualization import draw_dots logger = get_logger(__name__) +#: Public surface plus the late-bound collaborators the split-out modules +#: resolve through this facade (``mock.patch`` targets for the test suite). +__all__ = [ + "DEFAULT_EXPLORER_MODEL", + "Explorer", + "NATIVE_EXPLORER_TOOL_DECLARATIONS", + "StorageManager", + "UNIVERSAL_EXPLORER_TOOLS", + "_generate_content_with_reliability", + "_run_object_detection", + "draw_dots", + "genai", + "get_llm", + "is_ocr_configured", + "logger", + "perform_ocr", + "settings", +] + +#: Model used when the LLM configuration carries no Explorer entry. +DEFAULT_EXPLORER_MODEL = "gemini-3.8-flash" + +#: Environment override for Gemini explicit context caching ("true" / "false"). +CACHING_ENV_VAR = "ARTEMIS_EXPLORER_CACHING" + +#: How far outside its labeled element (as a fraction of the screen, per side) +#: a candidate's point may fall and still inherit that element's bounds. +LABEL_TOLERANCE_RATIO = 0.02 + async def _generate_content_with_reliability(operation, *, label: str = "Explorer model call"): """Run one native google-genai model call under the shared reliability layer. @@ -111,9 +124,7 @@ async def _generate_content_with_reliability(operation, *, label: str = "Explore attempt += 1 failure = classify_failure(call_err) if not failure.retryable: - logger.error( - f"{label} permanently failed [{failure.category.value}]: {call_err}" - ) + logger.error(f"{label} permanently failed [{failure.category.value}]: {call_err}") _record_llm_event( "llm_gave_up", { @@ -132,8 +143,7 @@ async def _generate_content_with_reliability(operation, *, label: str = "Explore policy = retry_policy_for(failure.category) if attempt >= policy.max_attempts: logger.error( - f"{label} exhausted {attempt} attempt(s)" - f" [{failure.category.value}]: {call_err}" + f"{label} exhausted {attempt} attempt(s) [{failure.category.value}]: {call_err}" ) _record_llm_event( "llm_gave_up", @@ -169,6 +179,15 @@ async def _generate_content_with_reliability(operation, *, label: str = "Explore class Explorer(PerceptionToolsMixin, UniversalRunnerMixin, RunSetupMixin, NativeRunnerMixin): + """Locates UI elements on a screenshot for the ``ask_explorer`` tool. + + The tier (flash / pro / ultra) is applied per ``run`` call from + :data:`artemis.agents.explorer.tiers.EXPLORER_TIERS`; it decides the + engine, the turn budget, the exposed perception tools and the caching + default. Construction is side-effect free: no network client is created + until the native Gemini branch actually needs one. + """ + DENYLIST_TEMPLATE = ( "\n# TOOL DENYLIST\n- The following tools are denylisted and cannot be used: {tools}\n" ) @@ -177,61 +196,150 @@ class Explorer(PerceptionToolsMixin, UniversalRunnerMixin, RunSetupMixin, Native def __init__(self, ctx: ArtemisContext): self.ctx = ctx self.global_label_idx = 1 - self.width = 1080 - self.height = 2400 - self.image_name = None - self.screenshot_path = None - self.image_pool = {} + self.width, self.height = FALLBACK_SCREEN_SIZE + self.image_name: str | None = None + self.screenshot_path: str | None = None + self.image_pool: dict[str, dict[str, Any]] = {} self.next_img_id = 1 + self.http_client = None + self.client = None + self.turn_latencies: list[float] = [] + self.turn_cached_tokens: list[int] = [] + self.trace_history: list[dict[str, Any]] = [] + + # Rebuilt per ``run``: the queryable snapshot of the screen, and the + # element behind every label the model has been shown so far. + self.screen_index: ScreenIndex = ScreenIndex.empty(self.width, self.height) + self.label_registry: dict[str, ScreenElement] = {} + + # No tier is applied until ``run``; until then only the user's own + # denylist (plus unconfigured OCR) restricts the tool set. + self.tier: ExplorerTier | None = None + self.max_turns = 0 + self._user_denylist = self._load_user_denylist() + self.denylisted_tools = self._compute_denylist(None) + + self.model_name = self._resolve_model_name() + self.use_native_gemini = self._detect_native_engine() + + # ------------------------------------------------------------------ # + # Engine selection (no side effects) + # ------------------------------------------------------------------ # + + def _resolve_model_name(self) -> str: + """Returns the configured Explorer model name without a provider prefix.""" + llm_config = getattr(self.ctx, "llm_config", None) + llm_cfg = getattr(llm_config, "explorer", None) if llm_config else None + model = getattr(llm_cfg, "model", None) if llm_cfg else None + model_name = model if isinstance(model, str) and model else DEFAULT_EXPLORER_MODEL + if "/" in model_name: + model_name = model_name.split("/")[-1] + return model_name + + def _detect_native_engine(self) -> bool: + """True when the native google-genai SDK should drive the reasoning loop. + + A Gemini model alone is not enough: without a Google API key (or a + client already shared on the context) the SDK cannot be constructed, + so such configurations run through the universal LangChain engine. + """ + if "gemini" not in self.model_name.lower(): + return False + if getattr(self.ctx, "_genai_client", None) is not None: + return True + api_key = getattr(settings, "GOOGLE_API_KEY", None) + return bool(api_key and api_key.get_secret_value()) + + def _ensure_genai_client(self): + """Returns the context-level GenAI client, creating it on first use. + + Only the native branch calls this, so universal configurations never + touch ``genai`` (and never fail on a missing Google key). The client + is stored on the context for connection pooling across agents. + """ + client = getattr(self.ctx, "_genai_client", None) + if client is None: + logger.info("Initializing GenAI client on context for connection pooling (Explorer)") + api_key = getattr(settings, "GOOGLE_API_KEY", None) + client = genai.Client(api_key=api_key.get_secret_value() if api_key else None) + self.ctx._genai_client = client + self.client = client + return client + + # ------------------------------------------------------------------ # + # Tier application + # ------------------------------------------------------------------ # + + def _load_user_denylist(self) -> frozenset[str]: + """Reads the user-configured Explorer denylist from the agent config.""" try: agent_cfg = getattr(self.ctx, "agent_config", None) - denylisted_config = ( - getattr(agent_cfg, "denylisted_tools", {}).get("explorer", []) if agent_cfg else [] - ) - self.denylisted_tools = set(denylisted_config) + denylists = getattr(agent_cfg, "denylisted_tools", None) if agent_cfg else None + configured = denylists.get("explorer", []) if isinstance(denylists, dict) else [] + return frozenset(str(name) for name in configured) except (TypeError, AttributeError): - self.denylisted_tools = set() - self.http_client = None - self.turn_latencies = [] - self.turn_cached_tokens = [] - self.trace_history = [] - self._init_engine() + return frozenset() - def _init_engine(self) -> None: - """Initializes model engine and decides whether to use native Gemini or Universal path.""" - ctx = self.ctx - llm_config = getattr(ctx, "llm_config", None) - llm_cfg = getattr(llm_config, "explorer", None) if llm_config else None - model_str = ( - llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.7-flash" - ).lower() - self.model_name = ( - llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.7-flash" - ) - if "/" in self.model_name: - self.model_name = self.model_name.split("/")[-1] + def _compute_denylist(self, tier: ExplorerTier | None) -> set[str]: + """Tools the model must not see: tier-hidden, user-denylisted, unconfigured OCR.""" + denylist = set(self._user_denylist) + if tier is not None: + denylist |= tier.hidden_tools + if not is_ocr_configured(): + denylist.add("get_ocr_list") + return denylist - has_google_key = bool( - settings.GOOGLE_API_KEY and settings.GOOGLE_API_KEY.get_secret_value() - ) - is_gemini_model = "gemini" in model_str + def _apply_tier(self, tier: ExplorerTier) -> None: + """Adopts the tier's turn budget and tool exposure for this run.""" + self.tier = tier + self.max_turns = tier.max_turns + self.denylisted_tools = self._compute_denylist(tier) - self.client = getattr(ctx, "_genai_client", None) - if self.client is not None: - self.use_native_gemini = True - elif has_google_key and is_gemini_model: - try: - self.client = genai.Client(api_key=settings.GOOGLE_API_KEY.get_secret_value()) - ctx._genai_client = self.client - self.use_native_gemini = True - except Exception as e: - logger.warning( - f"Failed to initialize native Gemini client for Explorer: {e}." - " Using universal engine." - ) - self.use_native_gemini = False - else: - self.use_native_gemini = False + def _resolve_caching(self, tier: ExplorerTier, enable_caching: bool | None) -> bool: + """Resolves Gemini explicit context caching for this run. + + Precedence: explicit argument, ``ARTEMIS_EXPLORER_CACHING`` env + override, the agent configuration, the execution setup, the global + settings, then the tier default. Configuration values are only + trusted when they are real booleans so a mocked or malformed config + (``None``, sentinel objects) falls through instead of forcing a value. + """ + if isinstance(enable_caching, bool): + return enable_caching + env_value = os.getenv(CACHING_ENV_VAR, "").strip().lower() + if env_value in ("true", "false"): + return env_value == "true" + for source in ( + getattr(self.ctx, "agent_config", None), + getattr(self.ctx, "execution_setup", None), + ): + value = getattr(getattr(source, "explorer", None), "caching", None) + if isinstance(value, bool): + return value + value = getattr(settings, "EXPLORER_CACHING", None) + if isinstance(value, bool): + return value + return tier.caching + + def _hidden_tool_names(self) -> set[str]: + """Denylisted tools plus OCR when it is unconfigured, evaluated at call time. + + OCR availability is re-checked here (not only in ``_apply_tier``) so + callers that reset ``denylisted_tools`` still never see ``get_ocr_list`` + without a configured OCR backend. + """ + hidden = set(self.denylisted_tools) + if not is_ocr_configured(): + hidden.add("get_ocr_list") + return hidden + + def get_exposed_tools(self, only_submit: bool = False) -> list[types.FunctionDeclaration]: + """Native tool declarations the model may see for the current tier.""" + hidden = self._hidden_tool_names() + tools = [tool for tool in self.TOOLS if tool.name not in hidden] + if only_submit: + tools = [tool for tool in tools if tool.name == "submit_answer"] + return tools def _prune_historical_images(self, contents, keep_last=1): image_parts = [] @@ -253,15 +361,117 @@ class Explorer(PerceptionToolsMixin, UniversalRunnerMixin, RunSetupMixin, Native for p in content.parts ] - def get_exposed_tools(self, only_submit: bool = False) -> list[types.FunctionDeclaration]: - """Returns the list of tools, filtering out denylisted and unconfigured ones.""" - denylisted = set(self.denylisted_tools) - if not is_ocr_configured(): - denylisted.add("get_ocr_list") - tools = [tool for tool in self.TOOLS if tool.name not in denylisted] - if only_submit: - tools = [tool for tool in tools if tool.name == "submit_answer"] - return tools + # ------------------------------------------------------------------ # + # Label bounds and the structural pre-pass + # ------------------------------------------------------------------ # + + def _norm_bounds(self, element: ScreenElement) -> list[int] | None: + """The element's bounds on the 0-1000 grid, or None when they collapse. + + A box that rounds to zero width or height is useless to the caller's + hit test, so it is withheld rather than shipped as a degenerate rect. + """ + left, top, right, bottom = element.bounds + n_left, n_top = pixel_to_norm(left, top, self.width, self.height) + n_right, n_bottom = pixel_to_norm(right, bottom, self.width, self.height) + if n_right <= n_left or n_bottom <= n_top: + return None + return [n_left, n_top, n_right, n_bottom] + + def _enrich_candidates(self, candidates: list[Any]) -> list[Any]: + """Attaches the registered element's bounds to candidates that point at it. + + The label alone is not trusted: models occasionally reuse a label for + a different spot, so the coordinates must also fall inside the + element (padded by :data:`LABEL_TOLERANCE_RATIO` of the screen to + absorb rounding). Unknown labels, detection-only labels and points + outside the element pass through untouched. + """ + enriched: list[Any] = [] + for cand in candidates: + element = ( + self.label_registry.get(str(cand.get("label"))) if isinstance(cand, dict) else None + ) + coords = cand.get("coords") if element is not None else None + if element is None or not is_valid_norm_point(coords): + enriched.append(cand) + continue + px, py = norm_to_pixel(int(coords[0]), int(coords[1]), self.width, self.height) + left, top, right, bottom = element.bounds + pad_x = self.width * LABEL_TOLERANCE_RATIO + pad_y = self.height * LABEL_TOLERANCE_RATIO + inside = left - pad_x <= px <= right + pad_x and top - pad_y <= py <= bottom + pad_y + norm_bounds = self._norm_bounds(element) if inside else None + if norm_bounds is None: + enriched.append(cand) + continue + enriched.append({**cand, "bounds": norm_bounds, "source": element.source}) + return enriched + + def _structural_prepass(self, query: str) -> tuple[list[dict[str, Any]], list[str]]: + """Resolves the ``|``-separated parts of ``query`` that exactly name one element. + + Returns ``(candidates, unresolved_parts)``. Only a *unique* exact + label match is taken: several matches mean the caller's wording does + not disambiguate, which is exactly what the model is for. + """ + parts = [part.strip() for part in query.split("|") if part.strip()] + candidates: list[dict[str, Any]] = [] + unresolved: list[str] = [] + for part in parts: + hits = self.screen_index.exact_matches(part) + if len(hits) != 1: + unresolved.append(part) + continue + element = hits[0] + label = f"T{len(candidates) + 1}" + self._register_label(label, element) + cx, cy = element.center + candidate: dict[str, Any] = { + "label": label, + "coords": list(pixel_to_norm(cx, cy, self.width, self.height)), + } + norm_bounds = self._norm_bounds(element) + if norm_bounds is not None: + candidate["bounds"] = norm_bounds + candidate["source"] = element.source + candidate["description"] = ( + f"{element.text} (exact text match)" + if normalize_text(part) == normalize_text(element.text) + else f"{part} (exact text match: '{element.text}')" + ) + candidates.append(candidate) + return candidates, unresolved + + @staticmethod + def _merge_prepass(prepass: list[dict[str, Any]], raw: str) -> str: + """Prepends pre-pass candidates to the engine's outcome JSON. + + An engine answer that is not outcome-shaped (a bare error string) + becomes the ``fallback_message`` so the structural hits survive it. + """ + if not prepass: + return raw + try: + data = json.loads(raw) + except (TypeError, ValueError): + data = None + if not isinstance(data, dict): + return json.dumps( + {"candidates": prepass, "fallback_message": str(raw)}, ensure_ascii=False + ) + engine_candidates = data.get("candidates") or [] + return json.dumps( + { + "candidates": [*prepass, *engine_candidates], + "fallback_message": str(data.get("fallback_message") or ""), + }, + ensure_ascii=False, + ) + + # ------------------------------------------------------------------ # + # Entry point + # ------------------------------------------------------------------ # @trace(type="agent", name="explorer") async def run( @@ -271,41 +481,72 @@ class Explorer(PerceptionToolsMixin, UniversalRunnerMixin, RunSetupMixin, Native screenshot_path: str, state: State, minimal_list: str = "", - enable_caching: bool = False, + enable_caching: bool | None = None, version: Literal["flash", "pro", "ultra"] = "pro", ) -> str: - if version == "flash": - return await self._run_flash(query, screenshot_path) + """Locates ``query`` on ``screenshot_path`` under the ``version`` tier. - self.http_client = httpx.AsyncClient() - - # 1. Compute current image hash and verify with Data Engine - image_name, record = self._resolve_image_record(screenshot_path) - self.image_name = image_name + Every tier first indexes the screen in memory and tries the + structural pre-pass; only the parts of the query it cannot settle + reach the tier's engine. Returns the JSON string + ``{"candidates": [...], "fallback_message": "..."}`` consumed by + ``artemis.tools.explorer_tool``. + """ + tier = get_tier(version) + self._apply_tier(tier) self.screenshot_path = screenshot_path + self.global_label_idx = 1 + self.label_registry = {} - # Resolve parameters - mode, max_iterations = self._resolve_version_limits(version) + try: + fused_xml = await self._load_screen(screenshot_path, state, tier) + prepass, unresolved = self._structural_prepass(query) + if prepass and not unresolved: + logger.info( + f"Explorer pre-pass answered {query!r} from the UI tree without a model call." + ) + return json.dumps( + {"candidates": prepass, "fallback_message": ""}, ensure_ascii=False + ) + engine_query = " | ".join(unresolved) if prepass else query - # 2. Prepare Native Tools declarations - self._resolve_screen_dimensions(state) + if tier.is_oneshot: + raw = await self._run_flash(engine_query, screenshot_path) + else: + raw = await self._run_loop( + tier, + engine_query, + context_feedback, + screenshot_path, + state, + minimal_list, + enable_caching, + fused_xml, + ) + return self._merge_prepass(prepass, raw) + finally: + await self._close_http_client() - # Initialize Image Pool + async def _run_loop( + self, + tier: ExplorerTier, + query: str, + context_feedback: str, + screenshot_path: str, + state: State, + minimal_list: str, + enable_caching: bool | None, + fused_xml: list[dict[str, Any]], + ) -> str: + """Shared setup for the loop engines, then the engine-specific loop.""" self._init_image_pool(screenshot_path) - # 4. Initialize or reuse dynamic context-level GenAI client for connection pooling - client = self._ensure_genai_client() - - model_name, temperature, thinking_level, fallback_model = self._resolve_model_params() - - # 5. Construct Prompt & Initial Message List - prompt_template, prompt_error = self._build_prompt_template(version, mode, max_iterations) + prompt_template, prompt_error = self._build_prompt_template(tier) if prompt_error is not None: - return prompt_error + return self._failure_outcome(prompt_error) - # Generate initial visual annotations if minimal_list is empty - minimal_list, img_to_read = await self._prepare_initial_annotation( - record, state, screenshot_path, minimal_list, image_name + minimal_list, img_to_read = self._prepare_initial_annotation( + fused_xml, screenshot_path, minimal_list, self.image_name ) if not self.use_native_gemini: @@ -315,21 +556,33 @@ class Explorer(PerceptionToolsMixin, UniversalRunnerMixin, RunSetupMixin, Native screenshot_path=img_to_read, state=state, minimal_list=minimal_list, - version=version, prompt_template=prompt_template, - max_iterations=max_iterations, + max_turns=self.max_turns, ) + client = self._ensure_genai_client() + model_name, temperature, thinking_level, _fallback_model = self._resolve_model_params() return await self._run_native( client=client, query=query, context_feedback=context_feedback, minimal_list=minimal_list, img_to_read=img_to_read, - enable_caching=enable_caching, + enable_caching=self._resolve_caching(tier, enable_caching), prompt_template=prompt_template, model_name=model_name, temperature=temperature, thinking_level=thinking_level, - max_iterations=max_iterations, + max_turns=self.max_turns, ) + + async def _close_http_client(self) -> None: + """Releases the on-demand OCR HTTP client; both engines share this path.""" + client, self.http_client = self.http_client, None + if client is None: + return + try: + await client.aclose() + logger.info("Closed Explorer HTTP client.") + except Exception as close_err: + logger.warning(f"Failed to close Explorer HTTP client: {close_err}") diff --git a/artemis/agents/explorer/geometry.py b/artemis/agents/explorer/geometry.py new file mode 100644 index 0000000..7c4740a --- /dev/null +++ b/artemis/agents/explorer/geometry.py @@ -0,0 +1,73 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Screen geometry shared by the Explorer agent and the ``ask_explorer`` tool. + +Explorer candidates use normalized ``[x, y]`` coordinates on a 0-1000 scale; +device actions use pixels. +""" + +from typing import Any + +#: Fallback screen size when neither the operator observation nor the device +#: context reports one (a common phone portrait resolution). +FALLBACK_SCREEN_SIZE: tuple[int, int] = (1080, 2400) + +NORMALIZED_MAX = 1000 + + +def resolve_screen_size(ctx: Any, state: Any) -> tuple[int, int]: + """Returns ``(width, height)`` in pixels for the current screenshot. + + Precedence: the operator's latest raw observation (which measured the + screenshot it annotated), then the device context, then the fallback. + """ + raw = getattr(state, "operator_raw_data", None) or {} + width = raw.get("width") if isinstance(raw, dict) else None + height = raw.get("height") if isinstance(raw, dict) else None + if isinstance(width, int) and isinstance(height, int) and width > 0 and height > 0: + return width, height + + device = getattr(ctx, "device", None) + dev_w = getattr(device, "device_width", None) if device else None + dev_h = getattr(device, "device_height", None) if device else None + fb_w, fb_h = FALLBACK_SCREEN_SIZE + width = dev_w if isinstance(dev_w, int) and dev_w > 0 else fb_w + height = dev_h if isinstance(dev_h, int) and dev_h > 0 else fb_h + return width, height + + +def norm_to_pixel(nx: float, ny: float, width: int, height: int) -> tuple[int, int]: + """Maps normalized 0-1000 coordinates to clamped pixel coordinates.""" + px = int(max(0, min(width, nx * width / NORMALIZED_MAX))) + py = int(max(0, min(height, ny * height / NORMALIZED_MAX))) + return px, py + + +def pixel_to_norm(px: float, py: float, width: int, height: int) -> tuple[int, int]: + """Maps pixel coordinates to clamped normalized 0-1000 coordinates.""" + nx = int(max(0, min(NORMALIZED_MAX, px * NORMALIZED_MAX / width))) + ny = int(max(0, min(NORMALIZED_MAX, py * NORMALIZED_MAX / height))) + return nx, ny + + +def is_valid_norm_point(coords: Any) -> bool: + """True when ``coords`` is a 2-item ``[nx, ny]`` inside the 0-1000 range.""" + if not isinstance(coords, (list, tuple)) or len(coords) != 2: + return False + try: + nx, ny = int(coords[0]), int(coords[1]) + except (TypeError, ValueError): + return False + return 0 <= nx <= NORMALIZED_MAX and 0 <= ny <= NORMALIZED_MAX diff --git a/artemis/agents/explorer/native_runner.py b/artemis/agents/explorer/native_runner.py index 60d32d9..4b97f11 100644 --- a/artemis/agents/explorer/native_runner.py +++ b/artemis/agents/explorer/native_runner.py @@ -17,32 +17,95 @@ Split out of ``artemis.agents.explorer.explorer``: the native-SDK reasoning loop and its named phases (model invocation, submit_answer interception and validation, tool dispatching, failure mapping, resource cleanup), packaged as -a mixin consumed by ``Explorer``. Patched collaborators (``settings``, -``logger``, ``_generate_content_with_reliability``) are resolved through the -facade module at call time; see ``artemis.agents.explorer._facade``. +a mixin consumed by ``Explorer``. Every google-genai call goes through the +SDK's ``client.aio`` surface so uploads, cache management and generation +never block the event loop. Patched collaborators (``logger``, +``_generate_content_with_reliability``) are resolved through the facade +module at call time; see ``artemis.agents.explorer._facade``. """ import asyncio +from collections.abc import Awaitable, Callable import json import os import time +from typing import TYPE_CHECKING, Any from google.genai import types from artemis.agents.explorer._facade import facade +from artemis.agents.explorer.geometry import is_valid_norm_point +from artemis.agents.explorer.tiers import SUBMIT_TOOL from artemis.constants import SAFETY_SETTINGS_BLOCK_NONE from artemis.data_engine.trace import TraceSpan +#: Loads a local image as a ``Part`` (inline bytes or a File API reference). +ImagePartGetter = Callable[[str], Awaitable[types.Part]] + + +def _bare_tool_name(name: str) -> str: + """Strips a namespace prefix (``ns:tool``) some models add to tool names.""" + return name.split(":")[-1] if ":" in name else name + class NativeRunnerMixin: """Native Gemini SDK reasoning loop of :class:`Explorer`.""" - def _make_image_part_getter(self, client, use_file_api: bool, uploaded_files: list): + # This mixin is composed into ``Explorer``. Declaring the attributes and + # sibling-mixin methods it consumes makes that host contract visible to + # static analysis without adding runtime shims or changing MRO behavior. + if TYPE_CHECKING: + from artemis.context import ArtemisContext + + ctx: ArtemisContext + denylisted_tools: set[str] + turn_latencies: list[float] + turn_cached_tokens: list[int] + trace_history: list[dict[str, Any]] + + def get_exposed_tools( + self, only_submit: bool = False + ) -> list[types.FunctionDeclaration]: ... + + def _prune_historical_images(self, contents: list, keep_last: int = 1) -> None: ... + + def _enrich_candidates(self, candidates: list[Any]) -> list[Any]: ... + + async def exec_ask_perception_tool( + self, + search_query: str, + nx: int, + ny: int, + detect_queries: list[str], + ) -> dict[str, Any]: ... + + async def exec_detect_objects( + self, queries: list[str], target_image_id: str = "img_0" + ) -> dict[str, Any]: ... + + async def exec_ask_image_processor( + self, instruction: str, target_image_id: str = "img_0" + ) -> dict[str, Any]: ... + + async def exec_get_ocr_list(self) -> dict[str, Any]: ... + + async def exec_inspect_region( + self, + x_min: int, + y_min: int, + x_max: int, + y_max: int, + zoom_factor: float = 2.0, + ) -> dict[str, Any]: ... + + def _make_image_part_getter( + self, client, use_file_api: bool, uploaded_files: list + ) -> ImagePartGetter: """Builds the image-part loader used for screenshots and tool images.""" - def get_image_part(file_path: str) -> types.Part: + async def get_image_part(file_path: str) -> types.Part: if use_file_api: - file_ref = client.files.upload(file=file_path) + file_ref = await client.aio.files.upload(file=file_path) uploaded_files.append(file_ref) return types.Part( file_data=types.FileData( @@ -50,10 +113,9 @@ class NativeRunnerMixin: mime_type=file_ref.mime_type or "image/jpeg", ) ) - else: - with open(file_path, "rb") as f: - img_bytes = f.read() - return types.Part.from_bytes(data=img_bytes, mime_type="image/jpeg") + with open(file_path, "rb") as f: + img_bytes = f.read() + return types.Part.from_bytes(data=img_bytes, mime_type="image/jpeg") return get_image_part @@ -79,26 +141,14 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima ) ] - def _resolve_caching_flag(self, enable_caching): - """Resolves the effective caching flag from env or settings when unset.""" - _ex = facade() - if enable_caching is None: - env_cache = os.getenv("ARTEMIS_EXPLORER_CACHING", "").lower() - if env_cache in ["true", "false"]: - enable_caching = env_cache == "true" - else: - enable_caching = getattr(_ex.settings, "EXPLORER_CACHING", True) - return enable_caching - - def _create_cache_resource( + async def _create_cache_resource( self, client, model_name: str, prompt_template: str, contents: list ): """Creates the explicit cache resource; returns None on failure.""" _ex = facade() - cached_content = None try: _ex.logger.info("Creating cache resource for Explorer...") - cached_content = client.caches.create( + cached_content = await client.aio.caches.create( model=model_name, config=types.CreateCachedContentConfig( contents=[contents[0]], @@ -114,10 +164,10 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima ), ) _ex.logger.info(f"Cache resource created successfully: {cached_content.name}") + return cached_content except Exception as cache_err: _ex.logger.error(f"Failed to create cache resource: {cache_err}") - cached_content = None - return cached_content + return None async def _run_native( self, @@ -127,36 +177,30 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima context_feedback: str, minimal_list: str, img_to_read: str, - enable_caching, + enable_caching: bool, prompt_template: str, model_name: str, temperature, thinking_level, - max_iterations: int, + max_turns: int, ) -> str: """Runs the native Gemini path: cache setup, reasoning loop, cleanup.""" use_file_api = os.getenv("ARTEMIS_USE_FILE_API", "false").lower() == "true" - uploaded_files = [] + uploaded_files: list = [] get_image_part = self._make_image_part_getter(client, use_file_api, uploaded_files) cached_content = None try: - # Upload initial screenshot - initial_part = get_image_part(img_to_read) + initial_part = await get_image_part(img_to_read) contents = self._build_initial_contents( query, context_feedback, minimal_list, initial_part ) - # Caching initialization - enable_caching = self._resolve_caching_flag(enable_caching) - - cached_content = None if enable_caching: - cached_content = self._create_cache_resource( + cached_content = await self._create_cache_resource( client, model_name, prompt_template, contents ) - # 6. Execution Loop (Native SDK Tools Dispatching) agent_outcome = await self._native_loop( client=client, contents=contents, @@ -166,7 +210,7 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima model_name=model_name, temperature=temperature, thinking_level=thinking_level, - max_iterations=max_iterations, + max_turns=max_turns, ) except Exception as e: @@ -181,29 +225,29 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima *, client, contents: list, - get_image_part, + get_image_part: ImagePartGetter, cached_content, prompt_template: str, model_name: str, temperature, thinking_level, - max_iterations: int, + max_turns: int, ) -> str: """Main native reasoning loop: one model turn per iteration.""" _ex = facade() - iterations = 0 + turn = 0 agent_outcome = "" self.turn_latencies = [] self.turn_cached_tokens = [] self.trace_history = [] - while iterations < max_iterations: - iterations += 1 - _ex.logger.info(f"Iteration {iterations}: Invoking Native Gemini SDK for Explorer...") + while turn < max_turns: + turn += 1 + _ex.logger.info(f"Turn {turn}/{max_turns}: Invoking Native Gemini SDK for Explorer...") self._prune_historical_images(contents, keep_last=1) - is_final_turn = iterations == max_iterations + is_final_turn = turn == max_turns if is_final_turn: warning_msg = ( "\n[WARNING] This is your final iteration. You MUST" @@ -229,7 +273,7 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima ) turn_record = { - "iteration": iterations, + "iteration": turn, "thoughts": ("\n".join(thinking_parts) if thinking_parts else ""), "tool_calls": [], } @@ -237,25 +281,16 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima function_calls = response.function_calls if not function_calls: self._handle_missing_tool_calls( - contents, response, turn_record, text_parts, max_iterations, iterations + contents, response, turn_record, text_parts, max_turns, turn ) continue - # Record model's function call request in history - if response.candidates and response.candidates[0].content: - contents.append(response.candidates[0].content) - else: - contents.append( - types.Content( - role="model", - parts=[types.Part(function_call=fc) for fc in function_calls], - ) - ) + # Record the model's function call request in history + contents.append(self._model_content(response, function_calls)) - # Execute function calls - tool_response_parts = [] + tool_response_parts: list[types.Part] = [] - # 6.1 Intercept and validate submit_answer to manage task lifecycle + # Intercept and validate submit_answer to manage the task lifecycle submit_outcome = self._process_submit_answer( function_calls, turn_record, tool_response_parts ) @@ -268,18 +303,31 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima ) if tool_response_parts: - # Native Gemini SDK allows 'tool' role to return mixed parts + # Native Gemini SDK allows the 'user' role to return mixed parts contents.append(types.Content(role="user", parts=tool_response_parts)) self.trace_history.append(turn_record) - if iterations >= max_iterations and not agent_outcome: + if turn >= max_turns and not agent_outcome: agent_outcome = ( "Error: Explorer reached maximum iterations without a conclusive answer." ) return agent_outcome + @staticmethod + def _model_content(response, function_calls: list) -> types.Content: + """The model turn to record: the SDK's content when present, else a rebuild.""" + candidates = getattr(response, "candidates", None) + if candidates: + content = getattr(candidates[0], "content", None) + if content: + return content + return types.Content( + role="model", + parts=[types.Part(function_call=fc) for fc in function_calls], + ) + def _build_generate_config( self, *, @@ -290,36 +338,31 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima thinking_level, ) -> types.GenerateContentConfig: """Builds the per-turn generation config (cached vs full tool config).""" - thinking_kwargs = ( - {"thinking_config": types.ThinkingConfig(thinking_level=thinking_level)} - if thinking_level - else {} + thinking_config = ( + types.ThinkingConfig(thinking_level=thinking_level) if thinking_level else None ) if cached_content and not is_final_turn: return types.GenerateContentConfig( cached_content=cached_content.name, temperature=temperature, safety_settings=SAFETY_SETTINGS_BLOCK_NONE, - **thinking_kwargs, + thinking_config=thinking_config, ) - else: - return types.GenerateContentConfig( - system_instruction=prompt_template, - temperature=temperature, - safety_settings=SAFETY_SETTINGS_BLOCK_NONE, - tools=[ - types.Tool( - function_declarations=self.get_exposed_tools(only_submit=is_final_turn) - ) - ], - tool_config=types.ToolConfig( - function_calling_config=types.FunctionCallingConfig( - mode=types.FunctionCallingConfigMode.ANY - ), - include_server_side_tool_invocations=True, + return types.GenerateContentConfig( + system_instruction=prompt_template, + temperature=temperature, + safety_settings=SAFETY_SETTINGS_BLOCK_NONE, + tools=[ + types.Tool(function_declarations=self.get_exposed_tools(only_submit=is_final_turn)) + ], + tool_config=types.ToolConfig( + function_calling_config=types.FunctionCallingConfig( + mode=types.FunctionCallingConfigMode.ANY ), - **thinking_kwargs, - ) + include_server_side_tool_invocations=True, + ), + thinking_config=thinking_config, + ) @staticmethod def _extract_response_texts(response) -> tuple[list[str], list[str]]: @@ -424,29 +467,39 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima response, turn_record: dict, text_parts: list[str], - max_iterations: int, - iterations: int, + max_turns: int, + turn: int, ) -> None: - """Handles a turn where the model produced plain text instead of tool calls.""" + """Handles a turn where the model produced plain text instead of tool calls. + + Responses without candidates (safety blocks, empty replies) are + recorded as a synthetic model text part so the conversation history + stays well-formed for the retry. + """ _ex = facade() - _ex.logger.warning( - f"Explorer iteration {iterations}: Model hallucinated" - " plain text. Forcing retry." - ) + _ex.logger.warning(f"Explorer turn {turn}: Model hallucinated plain text. Forcing retry.") + model_text = "\n".join(text_parts) if text_parts else "" turn_record["tool_calls"].append( { "name": "hallucinated_plain_text", - "args": {"text": "\n".join(text_parts) if text_parts else ""}, + "args": {"text": model_text}, "response": { "error": ( - "Forced retry because model failed to call" - " submit_answer or any other tool" + "Forced retry because model failed to call submit_answer or any other tool" ) }, } ) self.trace_history.append(turn_record) - contents.append(response.candidates[0].content) + + candidates = getattr(response, "candidates", None) + model_content = getattr(candidates[0], "content", None) if candidates else None + if not model_content: + model_content = types.Content( + role="model", + parts=[types.Part.from_text(text=model_text or "(no response)")], + ) + contents.append(model_content) contents.append( types.Content( role="user", @@ -457,7 +510,7 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima " to submit your final answer yet. If" " you are not certain about the final" " answer, you can continue exploring" - f" {max_iterations - iterations} more" + f" {max_turns - turn} more" " time(s)." ) ) @@ -497,19 +550,17 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima " as an array of exactly 2 integers `[nx," " ny]`." ) - else: + elif not is_valid_norm_point(coords): try: nx, ny = int(coords[0]), int(coords[1]) - if not (0 <= nx <= 1000) or not (0 <= ny <= 1000): - errors.append( - f"Candidate '{label or i}' coordinates" - f" `[{nx}, {ny}]` must strictly be in" - " the `[0-1000]` normalized scale" - " range inclusive." - ) - except (ValueError, TypeError, IndexError): + except (ValueError, TypeError): + errors.append(f"Candidate '{label or i}' coordinates are invalid integers.") + else: errors.append( - f"Candidate '{label or i}' coordinates are invalid integers." + f"Candidate '{label or i}' coordinates" + f" `[{nx}, {ny}]` must strictly be in" + " the `[0-1000]` normalized scale" + " range inclusive." ) return errors @@ -524,12 +575,7 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima """ _ex = facade() submit_call = next( - ( - fc - for fc in function_calls - if (fc.name.split(":")[-1] if ":" in fc.name else fc.name) - == "submit_answer" - ), + (fc for fc in function_calls if _bare_tool_name(fc.name) == SUBMIT_TOOL), None, ) if not submit_call: @@ -543,14 +589,14 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima _ex.logger.warning(f"Explorer submit_answer validation failed: {errors}") turn_record["tool_calls"].append( { - "name": "submit_answer", + "name": SUBMIT_TOOL, "args": args, "response": {"error": ("Validation Failed:\n" + "\n".join(errors))}, } ) tool_response_parts.append( types.Part.from_function_response( - name="submit_answer", + name=SUBMIT_TOOL, response={ "error": ( "Validation Failed:\n" @@ -562,126 +608,106 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima }, ) ) - # Allow ReAct loop to continue so LLM can self-correct and re-submit + # Allow the ReAct loop to continue so the model can self-correct return None - else: - _ex.logger.info("Explorer submit_answer validation passed successfully!") - agent_outcome = json.dumps(args, ensure_ascii=False) - turn_record["tool_calls"].append( - { - "name": "submit_answer", - "args": args, - "response": {"result": "success"}, - } - ) - self.trace_history.append(turn_record) - return agent_outcome + + _ex.logger.info("Explorer submit_answer validation passed successfully!") + # Candidates submitted by a registered label inherit that element's + # bounds; the rest of the model's arguments are passed through as is. + outcome_args = dict(args) + outcome_args["candidates"] = self._enrich_candidates(candidates) + agent_outcome = json.dumps(outcome_args, ensure_ascii=False) + turn_record["tool_calls"].append( + {"name": SUBMIT_TOOL, "args": args, "response": {"result": "success"}} + ) + self.trace_history.append(turn_record) + return agent_outcome @staticmethod - def _append_tool_response_with_image( - name: str, res: dict, get_image_part, tool_response_parts: list + async def _append_tool_response_with_images( + name: str, + res: dict, + get_image_part: ImagePartGetter, + tool_response_parts: list, + *, + result_key: str = "result", ) -> None: - """Appends a tool result part plus its annotated image when present.""" - if res.get("image_path"): - tool_response_parts.append( - types.Part.from_function_response( - name=name, - response={ - "result": res.get("text"), - }, - ) - ) - tool_response_parts.append(get_image_part(res["image_path"])) - else: - tool_response_parts.append( - types.Part.from_function_response( - name=name, - response={"result": res.get("text")}, - ) - ) + """Appends a tool result part plus every annotated image it produced. + + Image loading failures are logged and skipped: the textual result is + still delivered, so one unreadable annotation cannot sink the turn. + """ + _ex = facade() + tool_response_parts.append( + types.Part.from_function_response(name=name, response={result_key: res.get("text")}) + ) + image_paths = [p for p in [res.get("image_path"), *(res.get("image_paths") or [])] if p] + for img_p in image_paths: + try: + tool_response_parts.append(await get_image_part(img_p)) + except Exception as e: + _ex.logger.warning(f"Failed to load image response part for {img_p}: {e}") async def _execute_native_tool( self, name: str, args: dict, - get_image_part, + get_image_part: ImagePartGetter, tool_call_trace: dict, tool_response_parts: list, ) -> None: """Executes one non-submit tool call and appends its response parts.""" - _ex = facade() if name == "ask_perception_tool": + search_query = args.get("search_query") + nx = args.get("nx") + ny = args.get("ny") + detect_queries = args.get("detect_queries") res = await self.exec_ask_perception_tool( - search_query=args.get("search_query"), - nx=args.get("nx"), - ny=args.get("ny"), - detect_queries=args.get("detect_queries"), + search_query=search_query if isinstance(search_query, str) else "", + nx=nx if isinstance(nx, int) else -1, + ny=ny if isinstance(ny, int) else -1, + detect_queries=detect_queries if isinstance(detect_queries, list) else [], ) tool_call_trace["response"] = res - tool_response_parts.append( - types.Part.from_function_response( - name=name, - response={ - "text": res.get("text"), - }, - ) + await self._append_tool_response_with_images( + name, res, get_image_part, tool_response_parts, result_key="text" ) - if res.get("image_paths"): - for img_p in res["image_paths"]: - try: - tool_response_parts.append(get_image_part(img_p)) - except Exception as e: - _ex.logger.warning( - f"Failed to load image response part for {img_p}: {e}" - ) - elif name == "detect_objects": res = await self.exec_detect_objects( - queries=args.get("queries"), + queries=args.get("queries") or [], target_image_id=args.get("target_image_id", "img_0"), ) tool_call_trace["response"] = res - self._append_tool_response_with_image(name, res, get_image_part, tool_response_parts) - + await self._append_tool_response_with_images( + name, res, get_image_part, tool_response_parts + ) elif name == "ask_image_processor": res = await self.exec_ask_image_processor( - instruction=args.get("instruction"), + instruction=args.get("instruction") or "", target_image_id=args.get("target_image_id", "img_0"), ) tool_call_trace["response"] = res - - tool_response_parts.append( - types.Part.from_function_response( - name=name, - response={"result": res.get("text")}, - ) + await self._append_tool_response_with_images( + name, res, get_image_part, tool_response_parts ) - if res.get("image_paths"): - for img_p in res["image_paths"]: - try: - tool_response_parts.append(get_image_part(img_p)) - except Exception as e: - _ex.logger.warning( - "Failed to upload image path" - f" {img_p} for ask_image_processor" - f" tool response: {e}" - ) - elif name == "get_ocr_list": res = await self.exec_get_ocr_list() tool_call_trace["response"] = res - self._append_tool_response_with_image(name, res, get_image_part, tool_response_parts) - + await self._append_tool_response_with_images( + name, res, get_image_part, tool_response_parts + ) elif name == "inspect_region": res = await self.exec_inspect_region( - x_min=args.get("x_min"), - y_min=args.get("y_min"), - x_max=args.get("x_max"), - y_max=args.get("y_max"), - zoom_factor=args.get("zoom_factor"), + x_min=args["x_min"], + y_min=args["y_min"], + x_max=args["x_max"], + y_max=args["y_max"], + zoom_factor=args.get("zoom_factor", 2.0), ) tool_call_trace["response"] = res - self._append_tool_response_with_image(name, res, get_image_part, tool_response_parts) + await self._append_tool_response_with_images( + name, res, get_image_part, tool_response_parts + ) else: tool_call_trace["response"] = {"error": f"Tool {name} not found"} tool_response_parts.append( @@ -694,37 +720,32 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima async def _dispatch_tool_calls( self, function_calls: list, - get_image_part, + get_image_part: ImagePartGetter, turn_record: dict, tool_response_parts: list, ) -> None: - """Dispatches all non-submit tool calls sequentially with tracing.""" + """Dispatches all non-submit tool calls sequentially with tracing. + + Denylisted names are refused here as well, even though they are + absent from the declarations, because models occasionally call tools + they were never offered. + """ _ex = facade() for fc in function_calls: - name = fc.name.split(":")[-1] if ":" in fc.name else fc.name - if name == "submit_answer": + name = _bare_tool_name(fc.name) + if name == SUBMIT_TOOL: continue args = fc.args or {} - tool_call_trace = { - "name": name, - "args": args, - } + tool_call_trace = {"name": name, "args": args} if name in self.denylisted_tools: _ex.logger.warning( - "Explorer attempted to call denylisted tool" - f" '{name}'. Blocking execution." + f"Explorer attempted to call denylisted tool '{name}'. Blocking execution." ) - tool_call_trace["response"] = { - "error": (f"Tool '{name}' is denylisted and unavailable.") - } + error = f"Tool '{name}' is denylisted and unavailable." + tool_call_trace["response"] = {"error": error} turn_record["tool_calls"].append(tool_call_trace) tool_response_parts.append( - types.Part.from_function_response( - name=fc.name, - response={ - "error": (f"Tool '{name}' is denylisted and unavailable.") - }, - ) + types.Part.from_function_response(name=fc.name, response={"error": error}) ) continue @@ -771,26 +792,18 @@ Initial marked UI elements list (corresponding to numbers ①, ②... in the ima ensure_ascii=False, ) - async def _cleanup_native_resources( - self, client, cached_content, uploaded_files: list - ) -> None: - """Closes the HTTP client and deletes cache/file resources.""" + async def _cleanup_native_resources(self, client, cached_content, uploaded_files: list) -> None: + """Deletes the cache resource and File API uploads created for this run.""" _ex = facade() - if self.http_client: - try: - await self.http_client.aclose() - _ex.logger.info("Closed Explorer HTTP client.") - except Exception as close_err: - _ex.logger.warning(f"Failed to close Explorer HTTP client: {close_err}") if cached_content: try: _ex.logger.info(f"Deleting cache resource: {cached_content.name}") - client.caches.delete(name=cached_content.name) + await client.aio.caches.delete(name=cached_content.name) except Exception as cleanup_cache_err: _ex.logger.warning(f"Failed to delete cache resource: {cleanup_cache_err}") for file_ref in uploaded_files: try: - client.files.delete(name=file_ref.name) + await client.aio.files.delete(name=file_ref.name) except Exception as cleanup_err: _ex.logger.warning(f"Failed to delete uploaded file {file_ref.name}: {cleanup_err}") _ex.logger.info(f"Explorer turn latencies: {self.turn_latencies}") diff --git a/artemis/agents/explorer/perception_tools.py b/artemis/agents/explorer/perception_tools.py index f502017..86ae6a0 100644 --- a/artemis/agents/explorer/perception_tools.py +++ b/artemis/agents/explorer/perception_tools.py @@ -16,10 +16,12 @@ Split out of ``artemis.agents.explorer.explorer``: the ``exec_*`` tool method group plus its search helpers, packaged as a mixin consumed by ``Explorer``. -Patched collaborators (``settings``, ``draw_dots``, ``search_ui_func``, -``search_by_coordinates_func``, ``_run_object_detection``, ``StorageManager``, -``logger``) are resolved through the facade module at call time; see -``artemis.agents.explorer._facade``. +Text search, the coordinate audit and the OCR inventory all answer from the +run's in-memory :class:`~artemis.agents.explorer.screen_index.ScreenIndex`; +no Data Engine round trip is involved, so an unsynced frame never degrades +to "no UI data". Patched collaborators (``settings``, ``draw_dots``, +``_run_object_detection``, ``logger``) are resolved through the facade module +at call time; see ``artemis.agents.explorer._facade``. """ import asyncio @@ -27,36 +29,40 @@ import glob import json from pathlib import Path import re +from typing import TYPE_CHECKING, Any import cv2 from artemis.agents.explorer._facade import facade +from artemis.agents.explorer.geometry import norm_to_pixel, pixel_to_norm +from artemis.agents.explorer.screen_index import ScreenElement, ScreenIndex, normalize_text from artemis.agents.image_processor.image_processor import ImageProcessor from artemis.data_engine.trace import trace +#: Text-search answer when the screen could not be indexed at all. +NO_UI_TREE_MESSAGE = "No UI-tree data is available for this screen; rely on visual detection." -def load_detector_templates() -> tuple[list, object]: + +def load_detector_templates() -> tuple[list[str], float]: """Load object-detector prompt templates and the optional global timeout. Shared by ``exec_detect_objects`` and the flash-mode ``run`` path; the two call sites historically carried an identical inline copy of this block. """ _ex = facade() - detector_prompt_path = ( - Path(__file__).parent.parent / "object_detector" / "object_detector.json" - ) - global_timeout = None + detector_prompt_path = Path(__file__).parent.parent / "object_detector" / "object_detector.json" + global_timeout = 30.0 try: with open(detector_prompt_path, encoding="utf-8") as f: detector_config = json.load(f) detector_templates = detector_config.get("templates", []) detector_instructions = detector_config.get("instructions", "") - global_timeout = detector_config.get("global_timeout", None) + raw_timeout = detector_config.get("global_timeout") + if isinstance(raw_timeout, (int, float)) and raw_timeout > 0: + global_timeout = float(raw_timeout) except Exception as e: _ex.logger.warning(f"Failed to load detector prompt config: {e}") - detector_templates = [ - "Point to the following objects in the provided image: {labels_str}." - ] + detector_templates = ["Point to the following objects in the provided image: {labels_str}."] detector_instructions = "" templates = [f"{t}\n\n{detector_instructions}" for t in detector_templates] @@ -66,158 +72,160 @@ def load_detector_templates() -> tuple[list, object]: class PerceptionToolsMixin: """Perception/vision tool method group of :class:`Explorer`.""" - async def _search_ui_helper(self, query: str, prefix: str = "S", color: str = "red") -> dict: + # Runtime initialization lives in ``Explorer``/``RunSetupMixin``. Keep a + # precise declaration here so this mixin's host requirements are explicit. + if TYPE_CHECKING: + from artemis.context import ArtemisContext + + ctx: ArtemisContext + global_label_idx: int + width: int + height: int + image_name: str | None + screenshot_path: str | None + image_pool: dict[str, dict[str, Any]] + next_img_id: int + screen_index: ScreenIndex + label_registry: dict[str, ScreenElement] + + # ------------------------------------------------------------------ # + # Label registry and annotation helpers + # ------------------------------------------------------------------ # + + def _register_label(self, label: str, element: ScreenElement) -> None: + """Remembers which element a label shown to the model stands for. + + Only labels backed by a real UI-tree or OCR element are registered; + detection points (``D``) carry no bounds and are deliberately absent. + """ + self.label_registry[label] = element + + def _registry_element(self, entry: dict[str, Any]) -> ScreenElement: + """Maps a numbered-list entry back to its indexed element. + + The index is preferred over rebuilding the element from the entry + because it carries the interaction flags the entry dropped. + """ + raw_bounds = entry["bounds"] + bounds = (int(raw_bounds[0]), int(raw_bounds[1]), int(raw_bounds[2]), int(raw_bounds[3])) + text = str(entry.get("text") or "") + source = "ocr" if entry.get("is_ocr") else "xml" + for element in self.screen_index.elements: + if ( + element.bounds == bounds + and element.source == source + and normalize_text(element.text) == normalize_text(text) + ): + return element + return ScreenElement( + text=text, + bounds=bounds, + source=source, + class_name=str(entry["class"]) if entry.get("class") else None, + resource_id=str(entry["resource_id"]) if entry.get("resource_id") else None, + ) + + def _next_output_path(self, subdir: str) -> Path: + """Next free ``images//_.jpg`` under the traces directory.""" _ex = facade() - if not self.image_name: - return { - "text": ( - "Error: UI data for the current screen is not found in Data" - " Engine. It might not be synced yet." - ), - "image_path": None, - } + out_dir = Path(_ex.settings.TRACES_PATH) / "images" / subdir + out_dir.mkdir(parents=True, exist_ok=True) + stem = self.image_name or "temp_image" + max_seq = 0 + for existing in glob.glob(str(out_dir / f"{stem}_*.jpg")): + match = re.search(r"_(\d+)\.jpg$", existing) + if match: + max_seq = max(max_seq, int(match.group(1))) + return out_dir / f"{stem}_{max_seq + 1}.jpg" - # 1. Try with high precision threshold - raw_res = _ex.search_ui_func(self.image_name, query, 0.7) - if "error" in raw_res: - return {"text": raw_res["error"], "image_path": None} + def _draw_labeled_dots( + self, subdir: str, points: list[list[int]], labels: list[str], color: str + ) -> str | None: + """Draws ``labels`` at ``points`` on the screenshot; returns the annotated path. - matches = raw_res.get("matches", []) + Returns None when the screenshot is missing or drawing fails: the + textual result is still delivered, so one bad annotation never sinks + the tool call. + """ + _ex = facade() + if not self.screenshot_path: + return None + try: + output_path = self._next_output_path(subdir) + _ex.draw_dots(self.screenshot_path, points, labels, str(output_path), color=color) + return str(output_path) + except Exception as e: + _ex.logger.warning(f"Failed to draw {subdir} dots: {e}") + return None - # 2. If fewer than 3 matches, try with a lower threshold and merge + # ------------------------------------------------------------------ # + # Screen-index searches + # ------------------------------------------------------------------ # + + async def _search_ui_helper(self, query: str, prefix: str = "S", color: str = "red") -> dict: + """Fuzzy text search over the screen index, annotated with labeled dots. + + A strict pass (0.7) keeps the answer short when the label is + unambiguous; when it yields fewer than three hits a lenient pass + (0.4) is merged in so near-misses (abbreviations, truncated labels) + still surface for the model to judge. + """ + if not len(self.screen_index): + return {"text": NO_UI_TREE_MESSAGE, "image_path": None} + + matches = list(self.screen_index.search_text(query, 0.7)) if len(matches) < 3: - raw_res_low = _ex.search_ui_func(self.image_name, query, 0.4) - if "error" not in raw_res_low: - low_matches = raw_res_low.get("matches", []) - existing_keys = { - ( - m.get("matched_text"), - tuple(m.get("bounds")) if m.get("bounds") else None, - ) - for m in matches - } - for m in low_matches: - key = ( - m.get("matched_text"), - tuple(m.get("bounds")) if m.get("bounds") else None, - ) - if key not in existing_keys: - matches.append(m) - existing_keys.add(key) + seen = {(m.matched_text, m.element.bounds) for m in matches} + for m in self.screen_index.search_text(query, 0.4): + key = (m.matched_text, m.element.bounds) + if key not in seen: + matches.append(m) + seen.add(key) if not matches: return {"text": "No matches found.", "image_path": None} - points = [] - labels = [] - text_output = [] - + points: list[list[int]] = [] + labels: list[str] = [] + text_output: list[str] = [] for m in matches: - m_type = m.get("type", "xml") - m_prefix = "O" if m_type == "ocr" else prefix + element = m.element + m_prefix = "O" if element.source == "ocr" else prefix label_id = f"{m_prefix}{self.global_label_idx}" self.global_label_idx += 1 - bounds = m.get("bounds") - matched_text = m.get("matched_text", "") - - if bounds and len(bounds) == 4: - left, top, right, bottom = bounds - cx_pixel = (left + right) // 2 - cy_pixel = (top + bottom) // 2 - - points.append([cx_pixel, cy_pixel]) - labels.append(label_id) - - cx_norm = int(max(0, min(1000, cx_pixel * 1000 / self.width))) - cy_norm = int(max(0, min(1000, cy_pixel * 1000 / self.height))) - - text_output.append(f"[{label_id}] '{matched_text}' at [{cx_norm},{cy_norm}]") - else: - text_output.append(f"[{label_id}] '{matched_text}' at unknown") - - if not points: - return {"text": " | ".join(text_output), "image_path": None} - - base_dir = _ex.settings.TRACES_PATH - images_dir = base_dir / "images" - search_ui_dir = images_dir / "search_ui" - search_ui_dir.mkdir(parents=True, exist_ok=True) - - existing_files = glob.glob(str(search_ui_dir / f"{self.image_name}_*.jpg")) - max_seq = 0 - for f in existing_files: - match = re.search(r"_(\d+)\.jpg$", f) - if match: - max_seq = max(max_seq, int(match.group(1))) - seq = max_seq + 1 - output_path = search_ui_dir / f"{self.image_name}_{seq}.jpg" - - try: - _ex.draw_dots( - self.screenshot_path, - points, - labels, - str(output_path), - color=color, - ) - image_path = str(output_path) - except Exception as e: - _ex.logger.warning(f"Failed to draw dots: {e}") - image_path = None + self._register_label(label_id, element) + cx, cy = element.center + points.append([cx, cy]) + labels.append(label_id) + nx, ny = pixel_to_norm(cx, cy, self.width, self.height) + text_output.append(f"[{label_id}] '{m.matched_text}' at [{nx},{ny}]") + image_path = self._draw_labeled_dots("search_ui", points, labels, color) return {"text": " | ".join(text_output), "image_path": image_path} async def _search_by_coords_helper( self, nx: int, ny: int, prefix: str = "X", color: str = "blue" ) -> dict: - _ex = facade() - if not self.image_name: - return { - "text": ( - "Error: UI data for the current screen is not found in Data" - " Engine. It might not be synced yet." - ), - "image_path": None, - } + """Coordinate audit: names what actually sits under a normalized point. - x = int(max(0, min(self.width, nx * self.width / 1000))) - y = int(max(0, min(self.height, ny * self.height / 1000))) - raw_res = _ex.search_by_coordinates_func(self.image_name, x, y) + UI-tree hits come innermost first (see ``ScreenIndex.elements_at``), + so the label is bound to the node a tap would reach. The blue dot is + drawn even without a hit so the model can see where it probed. + """ + x, y = norm_to_pixel(nx, ny, self.width, self.height) + found = self.screen_index.elements_at(x, y) label_id = f"{prefix}{self.global_label_idx}" self.global_label_idx += 1 + image_path = self._draw_labeled_dots("coords_audit", [[x, y]], [label_id], color) - # Draw a dot at the query coordinates - base_dir = _ex.settings.TRACES_PATH - images_dir = base_dir / "images" - coords_audit_dir = images_dir / "coords_audit" - coords_audit_dir.mkdir(parents=True, exist_ok=True) + if not found: + return {"text": f"No elements found at [{nx},{ny}].", "image_path": image_path} - existing_files = glob.glob(str(coords_audit_dir / f"{self.image_name}_*.jpg")) - max_seq = 0 - for f in existing_files: - match = re.search(r"_(\d+)\.jpg$", f) - if match: - max_seq = max(max_seq, int(match.group(1))) - seq = max_seq + 1 - output_path = coords_audit_dir / f"{self.image_name}_{seq}.jpg" - - try: - _ex.draw_dots( - self.screenshot_path, - [[x, y]], - [label_id], - str(output_path), - color=color, - ) - image_path = str(output_path) - except Exception as e: - _ex.logger.warning(f"Failed to draw blue dots: {e}") - image_path = None - - raw_res_str = str(raw_res).replace("\n", " | ").replace(" | | ", " | ") + self._register_label(label_id, found[0]) + described = " | ".join(element.describe() for element in found) return { - "text": (f"Matched element at [{nx},{ny}]: [{label_id}] | {raw_res_str}"), + "text": f"Matched element at [{nx},{ny}]: [{label_id}] | {described}", "image_path": image_path, } @@ -231,6 +239,11 @@ class PerceptionToolsMixin: ) -> dict: _ex = facade() try: + if not self.screenshot_path: + return { + "text": "Error: Original screenshot is not available.", + "image_path": None, + } target_info = self.image_pool.get(target_image_id) if not target_info: return { @@ -289,7 +302,7 @@ class PerceptionToolsMixin: if target_image_id != "img_0": text_output.append( "[NOTE: Coordinates are automatically mapped back to" - " the original 1080x2400 screen space from" + f" the original {self.width}x{self.height} screen space from" f" {target_image_id}]" ) @@ -301,6 +314,8 @@ class PerceptionToolsMixin: x_norm, y_norm = pos t_img = cv2.imread(target_path) + if t_img is None: + raise ValueError(f"Failed to read target image at {target_path}") t_h, t_w = t_img.shape[:2] # Pixels in target image @@ -390,7 +405,7 @@ class PerceptionToolsMixin: text_parts.append(f"Text Search Results are: {xml_text}") coord_text = results[1].get("text", "No elements found.") - if coord_text and coord_text != "No elements found.": + if coord_text and not coord_text.startswith("No elements found"): text_parts.append(f"Coordinate Search Results are: {coord_text}") obj_text = results[2].get("text", "No objects detected.") @@ -502,92 +517,42 @@ class PerceptionToolsMixin: "text": text, "image_paths": image_paths, } - else: - return { - "text": f"ImageProcessor error: {err_msg}", - "image_paths": [], - } - - output_path = result.get("output_path") - trans = result.get("transform") - new_image_id = f"img_{len(self.image_pool)}" - if output_path and trans: - self.image_pool[new_image_id] = { - "path": output_path, - "transform": trans, - } - - text_msg = ( - f"Image processed successfully. New image saved as" - f" '{new_image_id}'. You can now use '{new_image_id}' as" - " 'target_image_id' in subsequent tool calls." - ) return { - "text": text_msg, - "image_path": output_path, - "image_id": new_image_id, + "text": "ImageProcessor error: no output images were produced.", + "image_paths": [], } @trace(type="tool", name="get_ocr_list") async def exec_get_ocr_list(self, prefix: str = "O", color: str = "green") -> dict: - _ex = facade() - if not self.image_name: - return { - "text": ("Error: UI data for the current screen is not found in Data Engine."), - "image_path": None, - } - + """Lists every OCR-detected text on the screen with a labeled dot.""" try: - db_path = _ex.settings.DATA_ENGINE_DB_PATH - base_dir = _ex.settings.TRACES_PATH - storage = _ex.StorageManager(db_path=str(db_path), base_dir=base_dir) - - record = storage.get_ui_record(self.image_name) - if not record or not record.ocr_result: + ocr_elements = self.screen_index.ocr_elements + if not ocr_elements: return { "text": "No text elements detected on the screen.", "image_path": None, } - points = [] - labels = [] - text_output = [] - - for item in record.ocr_result: - text = item.get("text", "").strip() - bounds = item.get("bounds", []) - if text and len(bounds) == 4: - left, top, right, bottom = bounds - cx = (left + right) // 2 - cy = (top + bottom) // 2 - if 0 <= cx <= self.width and 0 <= cy <= self.height: - label_id = f"{prefix}{self.global_label_idx}" - self.global_label_idx += 1 - points.append([cx, cy]) - labels.append(label_id) - - cx_norm = int(max(0, min(1000, cx * 1000 / self.width))) - cy_norm = int(max(0, min(1000, cy * 1000 / self.height))) - - text_output.append(f"[{label_id}] '{text}' coords: [{cx_norm},{cy_norm}]") + points: list[list[int]] = [] + labels: list[str] = [] + text_output: list[str] = [] + for element in ocr_elements: + cx, cy = element.center + if not (0 <= cx <= self.width and 0 <= cy <= self.height): + continue + label_id = f"{prefix}{self.global_label_idx}" + self.global_label_idx += 1 + self._register_label(label_id, element) + points.append([cx, cy]) + labels.append(label_id) + nx, ny = pixel_to_norm(cx, cy, self.width, self.height) + text_output.append(f"[{label_id}] '{element.text}' coords: [{nx},{ny}]") if not points: - return { - "text": "No valid OCR results found.", - "image_path": None, - } + return {"text": "No valid OCR results found.", "image_path": None} - images_dir = base_dir / "images" - ocr_dir = images_dir / "ocr" - ocr_dir.mkdir(parents=True, exist_ok=True) - - output_path = ocr_dir / f"{self.image_name}_{self.global_label_idx}.jpg" - _ex.draw_dots(self.screenshot_path, points, labels, str(output_path), color=color) - - return { - "text": "\n".join(text_output), - "image_path": str(output_path), - } + image_path = self._draw_labeled_dots("ocr", points, labels, color) + return {"text": "\n".join(text_output), "image_path": image_path} except Exception as e: return { @@ -606,6 +571,11 @@ class PerceptionToolsMixin: ) -> dict: _ex = facade() try: + if not self.screenshot_path: + return { + "text": "Error: Original screenshot is not available.", + "image_path": None, + } px_start = int(max(0, min(self.width, x_min * self.width / 1000.0))) py_start = int(max(0, min(self.height, y_min * self.height / 1000.0))) px_end = int(max(0, min(self.width, x_max * self.width / 1000.0))) diff --git a/artemis/agents/explorer/run_setup.py b/artemis/agents/explorer/run_setup.py index c85d626..3933b88 100644 --- a/artemis/agents/explorer/run_setup.py +++ b/artemis/agents/explorer/run_setup.py @@ -14,15 +14,16 @@ """Setup phases of ``Explorer.run``. -Split out of ``artemis.agents.explorer.explorer``: the flash-mode fast path -and the named preparation phases executed before the reasoning loop (image -record resolution, version limits, screen dimensions, image pool, GenAI -client, model parameters, prompt construction, and initial screenshot +Split out of ``artemis.agents.explorer.explorer``: the flash-tier one-shot +detection path, the per-run screen loading shared by every tier (image record +resolution, screen dimensions, fused hierarchy, in-memory screen index) and +the named preparation phases executed before a reasoning loop (image pool, +model parameters, tier-aware prompt construction, initial screenshot annotation), packaged as a mixin consumed by ``Explorer``. Patched -collaborators (``settings``, ``StorageManager``, ``genai``, -``is_ocr_configured``, ``perform_ocr``, ``draw_dots``, -``_run_object_detection``, ``logger``) are resolved through the facade module -at call time; see ``artemis.agents.explorer._facade``. +collaborators (``settings``, ``StorageManager``, ``is_ocr_configured``, +``perform_ocr``, ``draw_dots``, ``_run_object_detection``, ``logger``) are +resolved through the facade module at call time; see +``artemis.agents.explorer._facade``. """ import base64 @@ -32,26 +33,85 @@ import json import os from pathlib import Path import re +from typing import TYPE_CHECKING, Any + +import httpx from artemis.agents.explorer._facade import facade -from artemis.agents.explorer.constants import EXPLORE_DESCRIPTIONS +from artemis.agents.explorer.constants import EXECUTION_CONSTRAINT_TEMPLATE +from artemis.agents.explorer.geometry import resolve_screen_size from artemis.agents.explorer.perception_tools import load_detector_templates +from artemis.agents.explorer.screen_index import ScreenElement, ScreenIndex +from artemis.agents.explorer.tiers import SUBMIT_TOOL, ExplorerTier from artemis.graph.state import State from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml -from artemis.utils.visualization import format_minimal_list_with_points +from artemis.utils.visualization import format_minimal_list_with_elements + +#: Prompt bullets may be plain strings or ``{"text": ..., "requires": [...]}`` +#: objects; the latter are emitted only when every listed tool is exposed. +PromptBullet = str | dict[str, Any] + + +def _bullet_text(bullet: PromptBullet, exposed_tools: set[str]) -> str | None: + """Returns the bullet text, or None when its tool requirements are unmet.""" + if isinstance(bullet, dict): + required = bullet.get("requires") or [] + if not all(tool in exposed_tools for tool in required): + return None + return str(bullet.get("text", "")) + return str(bullet) + + +def load_prompt_sections(prompt_path: Path) -> dict[str, Any]: + """Loads ``explorer.json`` tolerating ``//`` and ``/* */`` comments and trailing commas.""" + content = prompt_path.read_text(encoding="utf-8") + content = re.sub(r"(? set[str]: ... + + def _register_label(self, label: str, element: ScreenElement) -> None: ... + + def _registry_element(self, entry: dict[str, Any]) -> ScreenElement: ... + + @staticmethod + def _failure_outcome(message: str) -> str: + """The contract-shaped answer for a run that produced no candidates.""" + return json.dumps({"candidates": [], "fallback_message": message}, ensure_ascii=False) + async def _run_flash(self, query: str, screenshot_path: str) -> str: - """Flash mode: single-shot object detection without a reasoning loop.""" + """Flash tier: one-shot object detection without a reasoning loop. + + Several targets may be listed in ``query`` separated by ``|``; each + detection becomes a ``D`` candidate described by its detected label. + """ _ex = facade() templates, global_timeout = load_detector_templates() - self.screenshot_path = screenshot_path - self.image_name = None - queries = [q.strip() for q in query.split("|") if q.strip()] try: @@ -76,21 +136,12 @@ class RunSetupMixin: ) fallback_message = "" if candidates else f"Failed to detect: {query}" return json.dumps( - { - "candidates": candidates, - "fallback_message": fallback_message, - }, + {"candidates": candidates, "fallback_message": fallback_message}, ensure_ascii=False, ) except Exception as e: _ex.logger.error(f"Flash mode object detection failed: {e}") - return json.dumps( - { - "candidates": [], - "fallback_message": f"Flash mode detection error: {e}", - }, - ensure_ascii=False, - ) + return self._failure_outcome(f"Flash mode detection error: {e}") def _resolve_image_record(self, screenshot_path: str) -> tuple: """Computes the screenshot hash and looks it up in the Data Engine DB.""" @@ -124,60 +175,28 @@ class RunSetupMixin: return image_name, record - def _resolve_version_limits(self, version: str) -> tuple[str, int]: - """Resolves mode label, iteration budget, and the effective tool denylist.""" - _ex = facade() - mode = version.capitalize() - version_denylist = set() - if version == "ultra": - max_iterations = 8 - elif version == "pro": - max_iterations = 3 - version_denylist = { - "ask_image_processor", - "get_ocr_list", - "inspect_region", - "detect_objects", - } - else: - max_iterations = 3 - version_denylist = { - "ask_image_processor", - "get_ocr_list", - "inspect_region", - } - - if not _ex.is_ocr_configured(): - version_denylist.add("get_ocr_list") - - try: - denylisted_config = ( - self.ctx.agent_config.denylisted_tools.get("explorer", []) - if self.ctx.agent_config - else [] - ) - self.denylisted_tools = version_denylist.union(set(denylisted_config)) - except (TypeError, AttributeError): - self.denylisted_tools = version_denylist - - return mode, max_iterations - def _resolve_screen_dimensions(self, state: State) -> None: - """Resolves device screen dimensions from operator data or the device.""" - ctx = self.ctx - self.width = 1080 - self.height = 2400 - operator_raw_data = getattr(state, "operator_raw_data", {}) or {} - w_raw = operator_raw_data.get("width") - h_raw = operator_raw_data.get("height") - if isinstance(w_raw, int) and isinstance(h_raw, int): - self.width = w_raw - self.height = h_raw - else: - if ctx.device and getattr(ctx.device, "device_width", None): - self.width = ctx.device.device_width - if ctx.device and getattr(ctx.device, "device_height", None): - self.height = ctx.device.device_height + """Adopts the screenshot's pixel size (operator observation, device, fallback).""" + self.width, self.height = resolve_screen_size(self.ctx, state) + + async def _load_screen( + self, screenshot_path: str, state: State, tier: ExplorerTier + ) -> list[dict[str, Any]]: + """Loads the screen once per run: record, screen size, fused hierarchy, index. + + Every tier goes through here so the structural pre-pass and the + perception tools share one in-memory snapshot instead of asking the + Data Engine per call. On-the-fly OCR is reserved for loop tiers: + flash never exposes text tools and must stay a single round trip. + """ + image_name, record = self._resolve_image_record(screenshot_path) + self.image_name = image_name + self._resolve_screen_dimensions(state) + fused_xml = await self._build_fused_hierarchy( + record, state, screenshot_path, allow_ocr=not tier.is_oneshot + ) + self.screen_index = ScreenIndex.from_hierarchy(fused_xml, self.width, self.height) + return fused_xml def _init_image_pool(self, screenshot_path: str) -> None: """Seeds the Image Pool with the original screenshot as img_0.""" @@ -195,26 +214,11 @@ class RunSetupMixin: } self.next_img_id = 1 - def _ensure_genai_client(self): - """Initializes or reuses the context-level GenAI client for connection pooling.""" - _ex = facade() - ctx = self.ctx - client = getattr(ctx, "_genai_client", None) - if client is None: - _ex.logger.info( - "Initializing new GenAI client on context for connection pooling (Explorer)..." - ) - client = _ex.genai.Client( - api_key=_ex.settings.GOOGLE_API_KEY.get_secret_value() - if _ex.settings.GOOGLE_API_KEY - else None - ) - ctx._genai_client = client - return client - def _resolve_model_params(self) -> tuple: """Resolves model name, temperature, thinking level, and fallback model.""" - llm_cfg = self.ctx.llm_config.explorer + _ex = facade() + llm_config = getattr(self.ctx, "llm_config", None) + llm_cfg = getattr(llm_config, "explorer", None) if llm_config else None temperature = 0.1 fallback_model = None thinking_level = None @@ -231,70 +235,89 @@ class RunSetupMixin: if "/" in fallback_model: fallback_model = fallback_model.split("/")[-1] else: - model_name = "gemini-3.7-flash" + model_name = _ex.DEFAULT_EXPLORER_MODEL return model_name, temperature, thinking_level, fallback_model - def _build_prompt_template( - self, version: str, mode: str, max_iterations: int - ) -> tuple[str | None, str | None]: - """Builds the system prompt template; returns (prompt, error_message).""" + def _exposed_tool_names(self, tier: ExplorerTier) -> set[str]: + """Tools the model can actually call in this run (tier minus hidden).""" + return (set(tier.tools) | {SUBMIT_TOOL}) - self._hidden_tool_names() + + def _build_prompt_template(self, tier: ExplorerTier) -> tuple[str | None, str | None]: + """Builds the tier-aware system prompt; returns ``(prompt, error_message)``. + + Bullets whose ``requires`` list names a tool the model cannot call in + this tier are dropped, so the prompt never describes capabilities + that are absent from the tool declarations. Tier-hidden tools are + simply never mentioned; only the user's own denylist (restricted to + tools the tier would otherwise expose) is spelled out. + """ prompt_path = Path(__file__).parent / "explorer.json" if not prompt_path.exists(): return None, "Error: Explorer prompt template not found." try: - content = prompt_path.read_text(encoding="utf-8") - content_no_comments = re.sub(r"(? list[dict[str, Any]]: + """Loads (and if needed OCR-fuses) the UI hierarchy for the screenshot. + + Precedence: the Data Engine record for the screenshot hash, else the + state's latest hierarchy when the screenshot is the latest one. The + HTTP client backing on-the-fly OCR is created here on first need and + released by ``Explorer.run`` regardless of the engine used. + """ _ex = facade() - fused_xml = [] + fused_xml: Any = [] if record and getattr(record, "ui_tree", None): ui_tree = record.ui_tree ocr_results = getattr(record, "ocr_result", None) if ocr_results is None: - if _ex.is_ocr_configured(): + if allow_ocr and _ex.is_ocr_configured(): try: _ex.logger.info( "Previous screenshot OCR is missing. Running OCR on-the-fly..." ) with open(screenshot_path, "rb") as img_file: img_b64 = base64.b64encode(img_file.read()).decode("utf-8") + if self.http_client is None: + self.http_client = httpx.AsyncClient() ocr_results = await _ex.perform_ocr(img_b64, client=self.http_client) except Exception as ocr_err: _ex.logger.error( @@ -306,15 +329,18 @@ class RunSetupMixin: fused_xml = fuse_ocr_with_xml(ui_tree, ocr_results or []) _ex.logger.info("Successfully loaded and fused UI hierarchy for previous screenshot.") - elif hasattr(state, "latest_ui_hierarchy"): - if screenshot_path == getattr(state, "latest_screenshot", None): - fused_xml = state.latest_ui_hierarchy - return fused_xml + elif screenshot_path == getattr(state, "latest_screenshot", None): + fused_xml = getattr(state, "latest_ui_hierarchy", None) + return list(fused_xml) if isinstance(fused_xml, (list, tuple)) else [] def _annotate_initial_screenshot( self, fused_xml, screenshot_path: str, image_name, minimal_list: str ) -> tuple[str, object]: - """Draws labeled dots for the fused hierarchy; returns (minimal_list, marked_path).""" + """Draws labeled dots for the fused hierarchy; returns (minimal_list, marked_path). + + Every numbered label is registered against its element so a + candidate the model later submits by number inherits real bounds. + """ _ex = facade() ctx = self.ctx marked_path = None @@ -322,10 +348,13 @@ class RunSetupMixin: _ex.logger.info( "Explorer self-annotating initial screenshot using latest_ui_hierarchy..." ) - formatted_list, points, labels = format_minimal_list_with_points( + formatted_list, elements, labels = format_minimal_list_with_elements( fused_xml, self.width, self.height ) minimal_list = formatted_list + points = [list(entry["center"]) for entry in elements] + for label, entry in zip(labels, elements, strict=True): + self._register_label(label, self._registry_element(entry)) self.global_label_idx = len(points) + 1 base_dir = ( @@ -334,7 +363,6 @@ class RunSetupMixin: else None ) if not base_dir: - db_path = _ex.settings.DATA_ENGINE_DB_PATH base_dir = _ex.settings.TRACES_PATH images_dir = base_dir / "images" initial_marked_dir = images_dir / "initial_marked" @@ -354,15 +382,14 @@ class RunSetupMixin: # Draw dots on the raw screenshot _ex.draw_dots(screenshot_path, points, labels, str(marked_path)) _ex.logger.info( - f"Successfully drew {len(points)} dots and saved marked" - f" image to {marked_path}" + f"Successfully drew {len(points)} dots and saved marked image to {marked_path}" ) except Exception as e: _ex.logger.error(f"Failed to self-annotate initial screenshot: {e}") return minimal_list, marked_path - async def _prepare_initial_annotation( - self, record, state: State, screenshot_path: str, minimal_list: str, image_name + def _prepare_initial_annotation( + self, fused_xml, screenshot_path: str, minimal_list: str, image_name ) -> tuple[str, str]: """Generates initial visual annotations when no minimal list is provided. @@ -370,12 +397,10 @@ class RunSetupMixin: model should read (marked screenshot when available, raw otherwise). """ marked_path = None - if not minimal_list: - fused_xml = await self._build_fused_hierarchy(record, state, screenshot_path) - if fused_xml: - minimal_list, marked_path = self._annotate_initial_screenshot( - fused_xml, screenshot_path, image_name, minimal_list - ) + if not minimal_list and fused_xml: + minimal_list, marked_path = self._annotate_initial_screenshot( + fused_xml, screenshot_path, image_name, minimal_list + ) if marked_path and os.path.exists(str(marked_path)): img_to_read = str(marked_path) diff --git a/artemis/agents/explorer/screen_index.py b/artemis/agents/explorer/screen_index.py new file mode 100644 index 0000000..837082a --- /dev/null +++ b/artemis/agents/explorer/screen_index.py @@ -0,0 +1,266 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""In-memory index of the current screen's UI elements for the Explorer. + +Built from the fused XML/OCR hierarchy so searches do not depend on the Data +Engine having persisted the frame. Supports fuzzy text search, exact matches +and coordinate hit testing. +""" + +from dataclasses import dataclass, field +import difflib +import re +from typing import Any, Literal + +from artemis.utils.visualization import parse_bounds + +Bounds = tuple[int, int, int, int] +ElementSource = Literal["xml", "ocr"] + +#: Attributes that mark an XML node as something the user can interact with. +INTERACTION_KEYS = ( + "clickable", + "scrollable", + "long-clickable", + "checkable", + "focusable", + "editable", + "selected", +) + +#: Minimum query length for substring matches to count as strong evidence; +#: shorter fragments ("ok", "a") match far too much. +_SUBSTRING_MIN_CHARS = 3 +_SUBSTRING_SCORE = 0.85 + + +def normalize_text(value: Any) -> str: + """Case-folds and collapses whitespace so visually equal labels compare equal.""" + return re.sub(r"\s+", " ", str(value or "")).strip().casefold() + + +def text_similarity(query: str, text: str) -> float: + """Similarity in ``[0, 1]`` between a query and an element label. + + Uses the Data Engine's ``SequenceMatcher`` ratio, with a minimum score + for substring matches when the query is at least three characters long. + """ + q, t = normalize_text(query), normalize_text(text) + if not q or not t: + return 0.0 + ratio = difflib.SequenceMatcher(None, q, t).ratio() + if len(q) >= _SUBSTRING_MIN_CHARS and (q in t or t in q): + ratio = max(ratio, _SUBSTRING_SCORE) + return ratio + + +def _is_truthy_attr(value: Any) -> bool: + return value is True or str(value).strip().lower() == "true" + + +@dataclass(frozen=True) +class ScreenElement: + """One text-bearing element of the screen with pixel bounds.""" + + text: str + bounds: Bounds + source: ElementSource + class_name: str | None = None + resource_id: str | None = None + interactive: bool = False + node: dict[str, Any] = field(default_factory=dict, compare=False, hash=False, repr=False) + + @property + def center(self) -> tuple[int, int]: + left, top, right, bottom = self.bounds + return (left + right) // 2, (top + bottom) // 2 + + @property + def area(self) -> int: + left, top, right, bottom = self.bounds + return max(1, right - left) * max(1, bottom - top) + + def contains(self, x: int, y: int) -> bool: + left, top, right, bottom = self.bounds + return left <= x <= right and top <= y <= bottom + + def overlap_ratio(self, other: "ScreenElement") -> float: + """Intersection over the smaller area; 1.0 when one box covers the other.""" + l1, t1, r1, b1 = self.bounds + l2, t2, r2, b2 = other.bounds + inter_w = min(r1, r2) - max(l1, l2) + inter_h = min(b1, b2) - max(t1, t2) + if inter_w <= 0 or inter_h <= 0: + return 0.0 + return (inter_w * inter_h) / min(self.area, other.area) + + def describe(self) -> str: + """Compact one-line description for tool text output.""" + parts = [f"'{self.text}'"] if self.text else [] + if self.class_name: + parts.append(f"class={self.class_name}") + if self.resource_id: + parts.append(f"id={self.resource_id}") + parts.append("source=ocr" if self.source == "ocr" else "source=ui-tree") + if self.interactive: + parts.append("interactive") + return " ".join(parts) + + +@dataclass(frozen=True) +class TextMatch: + element: ScreenElement + score: float + matched_text: str + + +class ScreenIndex: + """Queryable snapshot of the visible, text-bearing elements of one screen.""" + + def __init__(self, elements: list[ScreenElement], width: int, height: int): + self.elements = elements + self.width = width + self.height = height + + def __len__(self) -> int: + return len(self.elements) + + @property + def ocr_elements(self) -> list[ScreenElement]: + return [e for e in self.elements if e.source == "ocr"] + + # ------------------------------------------------------------------ # + # Construction + # ------------------------------------------------------------------ # + + @classmethod + def empty(cls, width: int, height: int) -> "ScreenIndex": + return cls([], width, height) + + @classmethod + def from_hierarchy( + cls, fused_xml: list[dict[str, Any]] | None, width: int, height: int + ) -> "ScreenIndex": + """Builds the index from a fused UI hierarchy (XML nodes plus OCR text). + + Off-screen and zero-area boxes are dropped, and the same label at the + same place is kept once. Nodes without any label are skipped: the + index exists to answer text and hit-test queries, and an unlabeled + wrapper answers neither usefully. + """ + elements: list[ScreenElement] = [] + seen: set[tuple[str, Bounds]] = set() + + def add(element: ScreenElement) -> None: + key = (normalize_text(element.text), element.bounds) + if key in seen or not cls._is_visible(element.bounds, width, height): + return + seen.add(key) + elements.append(element) + + for node in fused_xml or []: + if not isinstance(node, dict): + continue + class_name = node.get("class") or node.get("className") + resource_id = node.get("resource-id") or node.get("resourceId") + interactive = any(_is_truthy_attr(node.get(key)) for key in INTERACTION_KEYS) + + for ocr in node.get("ocr_elements") or []: + ocr_bounds = parse_bounds(ocr.get("bounds")) if isinstance(ocr, dict) else None + ocr_text = str(ocr.get("text") or "").strip() if isinstance(ocr, dict) else "" + if ocr_bounds and ocr_text: + add( + ScreenElement( + text=ocr_text, + bounds=ocr_bounds, + source="ocr", + class_name=str(class_name) if class_name else None, + resource_id=str(resource_id) if resource_id else None, + interactive=interactive, + node=node, + ) + ) + + label = str(node.get("text") or node.get("content-desc") or "").strip() + bounds = parse_bounds(node.get("bounds")) + if label and bounds: + add( + ScreenElement( + text=label, + bounds=bounds, + source="xml", + class_name=str(class_name) if class_name else None, + resource_id=str(resource_id) if resource_id else None, + interactive=interactive, + node=node, + ) + ) + return cls(elements, width, height) + + @staticmethod + def _is_visible(bounds: Bounds, width: int, height: int) -> bool: + left, top, right, bottom = bounds + if right <= left or bottom <= top: + return False + return right > 0 and bottom > 0 and left < width and top < height + + # ------------------------------------------------------------------ # + # Queries + # ------------------------------------------------------------------ # + + def search_text(self, query: str, threshold: float = 0.6, limit: int = 8) -> list[TextMatch]: + """Fuzzy label search, best matches first.""" + matches: list[TextMatch] = [] + for element in self.elements: + score = text_similarity(query, element.text) + if score > threshold: + matches.append(TextMatch(element=element, score=score, matched_text=element.text)) + matches.sort(key=lambda m: (-m.score, m.element.area)) + return matches[:limit] + + def exact_matches(self, query: str) -> list[ScreenElement]: + """Elements whose label equals ``query`` after normalization. + + Overlapping duplicates (the same label from the UI tree and from OCR + at the same place) collapse to one element, preferring the UI-tree + node because it carries structure the OCR box lacks. + """ + wanted = normalize_text(query) + if not wanted: + return [] + hits = [e for e in self.elements if normalize_text(e.text) == wanted] + hits.sort(key=lambda e: (e.source != "xml", e.area)) + unique: list[ScreenElement] = [] + for element in hits: + if all(element.overlap_ratio(kept) < 0.5 for kept in unique): + unique.append(element) + return unique + + def elements_at(self, x: int, y: int) -> list[ScreenElement]: + """Elements covering pixel ``(x, y)``: UI-tree nodes innermost first, then OCR. + + Mirrors the historical coordinate audit: UI-tree candidates stop at the + first interactive node (at most two are reported) so the answer names + what a tap would actually hit rather than every enclosing wrapper. + """ + covering = [e for e in self.elements if e.contains(x, y)] + xml = sorted((e for e in covering if e.source == "xml"), key=lambda e: e.area) + picked: list[ScreenElement] = [] + for element in xml: + picked.append(element) + if element.interactive or len(picked) >= 2: + break + ocr = sorted((e for e in covering if e.source == "ocr"), key=lambda e: e.area) + return picked + ocr diff --git a/artemis/agents/explorer/tiers.py b/artemis/agents/explorer/tiers.py new file mode 100644 index 0000000..dd4dec6 --- /dev/null +++ b/artemis/agents/explorer/tiers.py @@ -0,0 +1,110 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Engine, turn budget, tools and caching settings for each Explorer tier. + +User configuration selects the tier. The calling agent sees the same +``ask_explorer`` contract at every tier. +""" + +from dataclasses import dataclass +from typing import Literal + +from artemis.config.constants import ExplorerVersion + +ExplorerEngine = Literal["oneshot", "loop"] + +#: Tool that terminates a reasoning loop; always exposed by loop engines. +SUBMIT_TOOL = "submit_answer" + +#: Every perception / transformation tool the loop engines can expose. +PERCEPTION_TOOLS: frozenset[str] = frozenset( + { + "ask_perception_tool", + "detect_objects", + "get_ocr_list", + "inspect_region", + "ask_image_processor", + } +) + + +@dataclass(frozen=True) +class ExplorerTier: + """Behavioral profile of one Explorer tier.""" + + name: ExplorerVersion + engine: ExplorerEngine + #: Reasoning turns for the loop engine (1 for one-shot detection). + max_turns: int + #: Perception tools exposed to the loop engine (``submit_answer`` is implicit). + tools: frozenset[str] + #: Default for Gemini explicit context caching when the user did not decide. + caching: bool + #: One-line summary used in logs and configuration help. + summary: str + + @property + def is_oneshot(self) -> bool: + return self.engine == "oneshot" + + @property + def hidden_tools(self) -> frozenset[str]: + """Perception tools this tier does not expose.""" + return PERCEPTION_TOOLS - self.tools + + +EXPLORER_TIERS: dict[ExplorerVersion, ExplorerTier] = { + "flash": ExplorerTier( + name="flash", + engine="oneshot", + max_turns=1, + tools=frozenset(), + caching=False, + summary="One-shot visual detection on the current screenshot; fastest, no reasoning loop.", + ), + "pro": ExplorerTier( + name="pro", + engine="loop", + max_turns=3, + tools=frozenset({"ask_perception_tool"}), + caching=False, + summary=( + "Short reasoning loop combining UI-tree search, coordinate audit and visual" + " detection; balanced default." + ), + ), + "ultra": ExplorerTier( + name="ultra", + engine="loop", + max_turns=8, + tools=PERCEPTION_TOOLS, + caching=True, + summary=( + "Deep reasoning loop with zooming, OCR and pixel-level image processing for" + " layout-critical searches; slowest." + ), + ), +} + +DEFAULT_TIER: ExplorerVersion = "flash" + + +def get_tier(version: str | None) -> ExplorerTier: + """Returns the tier for ``version``; unknown or empty names fall back to the default.""" + key = str(version or "").strip().lower() + for tier in EXPLORER_TIERS.values(): + if tier.name == key: + return tier + return EXPLORER_TIERS[DEFAULT_TIER] diff --git a/artemis/agents/explorer/tool_declarations.py b/artemis/agents/explorer/tool_declarations.py index fee1e1e..e8c5969 100644 --- a/artemis/agents/explorer/tool_declarations.py +++ b/artemis/agents/explorer/tool_declarations.py @@ -30,7 +30,7 @@ UNIVERSAL_EXPLORER_TOOLS = [ "name": "ask_perception_tool", "description": ( "[Perception] Concurrently executes and awaits three sub-tasks in parallel," - " including database search, coordinate search, and visual object detection." + " including UI-tree text search, coordinate audit, and visual object detection." ), "parameters": { "type": "object", @@ -154,7 +154,7 @@ UNIVERSAL_EXPLORER_TOOLS = [ "properties": { "label": { "type": "string", - "description": "Reference label (e.g. S1, D2, T3)", + "description": "Reference label (e.g. X1, O2, D3, or a number like 3)", }, "description": { "type": "string", @@ -191,7 +191,7 @@ NATIVE_EXPLORER_TOOL_DECLARATIONS = [ name="ask_perception_tool", description=( "[Perception] Concurrently executes and awaits three sub-tasks" - " in parallel, including database search, coordinate search," + " in parallel, including UI-tree text search, coordinate audit," " and visual object detection on the screen.\n\nOutput" " Format:\nReturns:'text' (str): A single string consolidating" " results with 0-1000 normalized coordinates, including UI" @@ -264,7 +264,7 @@ NATIVE_EXPLORER_TOOL_DECLARATIONS = [ "[Perception] Retrieves all text elements detected on the" " original, unprocessed screen via OCR. Returns their" " normalized coordinates (in a [0, 1000] scale) labeled with" - " [T1], [T2] etc. in the text output, along with an annotated" + " [O1], [O2] etc. in the text output, along with an annotated" " image containing corresponding visual label dots." ), parameters=types.Schema( @@ -324,7 +324,7 @@ NATIVE_EXPLORER_TOOL_DECLARATIONS = [ "label": types.Schema( type=types.Type.STRING, description=( - "Reference label (e.g., S1, D2, T3, or number like 3)" + "Reference label (e.g. X1, O2, D3, or a number like 3)" ), ), "coords": types.Schema( diff --git a/artemis/agents/explorer/universal_runner.py b/artemis/agents/explorer/universal_runner.py index 6eee3d0..6dbfc4c 100644 --- a/artemis/agents/explorer/universal_runner.py +++ b/artemis/agents/explorer/universal_runner.py @@ -16,15 +16,15 @@ Split out of ``artemis.agents.explorer.explorer``: the ``_run_universal`` reasoning loop, packaged as a mixin consumed by ``Explorer``. Patched -collaborators (``get_llm``, ``is_ocr_configured``, ``logger``, -``UNIVERSAL_EXPLORER_TOOLS``) are resolved through the facade module at call -time; see ``artemis.agents.explorer._facade``. +collaborators (``get_llm``, ``logger``, ``UNIVERSAL_EXPLORER_TOOLS``) are +resolved through the facade module at call time; see +``artemis.agents.explorer._facade``. """ import asyncio import base64 import json -from typing import Any +from typing import TYPE_CHECKING, Any from langchain_core.messages import ( BaseMessage, @@ -34,46 +34,74 @@ from langchain_core.messages import ( ) from artemis.agents.explorer._facade import facade +from artemis.agents.explorer.geometry import is_valid_norm_point +from artemis.agents.explorer.tiers import SUBMIT_TOOL from artemis.graph.state import State class UniversalRunnerMixin: """Universal-engine reasoning loop of :class:`Explorer`.""" + if TYPE_CHECKING: + from artemis.context import ArtemisContext + + ctx: ArtemisContext + denylisted_tools: set[str] + + def _hidden_tool_names(self) -> set[str]: ... + + def _enrich_candidates(self, candidates: list[Any]) -> list[Any]: ... + + async def exec_ask_perception_tool( + self, search_query: str, nx: int, ny: int, detect_queries: list[str] + ) -> dict[str, Any]: ... + + async def exec_detect_objects( + self, queries: list[str], target_image_id: str = "img_0" + ) -> dict[str, Any]: ... + + async def exec_ask_image_processor( + self, instruction: str, target_image_id: str = "img_0" + ) -> dict[str, Any]: ... + + async def exec_get_ocr_list(self) -> dict[str, Any]: ... + + async def exec_inspect_region( + self, x_min: int, y_min: int, x_max: int, y_max: int, zoom_factor: float = 2.0 + ) -> dict[str, Any]: ... + + def _universal_exposed_tools(self) -> list[dict[str, Any]]: + """OpenAI-style tool schemas the model may see (tier-hidden ones excluded).""" + _ex = facade() + hidden = self._hidden_tool_names() + return [t for t in _ex.UNIVERSAL_EXPLORER_TOOLS if t["function"]["name"] not in hidden] + async def _run_universal( self, + *, query: str, context_feedback: str, screenshot_path: str, state: State, minimal_list: str, - version: str, prompt_template: str, - max_iterations: int, + max_turns: int, ) -> str: - """Executes Explorer reasoning loop via Universal LangChain ChatModel.""" + """Executes the Explorer reasoning loop via a universal LangChain ChatModel.""" _ex = facade() llm = _ex.get_llm(self.ctx, name="explorer") - # Filter universal tools based on denylist and OCR configuration - denylisted = set(self.denylisted_tools) - if not _ex.is_ocr_configured(): - denylisted.add("get_ocr_list") - exposed_tools = [ - t for t in _ex.UNIVERSAL_EXPLORER_TOOLS if t["function"]["name"] not in denylisted - ] + exposed_tools = self._universal_exposed_tools() bound_llm = llm.bind_tools(exposed_tools) messages = self._build_universal_messages( query, context_feedback, screenshot_path, minimal_list, prompt_template ) - iterations = 0 - agent_outcome = "" - - while iterations < max_iterations: - iterations += 1 - is_final_turn = iterations == max_iterations + turn = 0 + while turn < max_turns: + turn += 1 + is_final_turn = turn == max_turns if is_final_turn: messages.append( @@ -85,7 +113,7 @@ class UniversalRunnerMixin: ) ) submit_only_tools = [ - t for t in exposed_tools if t["function"]["name"] == "submit_answer" + t for t in exposed_tools if t["function"]["name"] == SUBMIT_TOOL ] current_llm = llm.bind_tools(submit_only_tools) if submit_only_tools else bound_llm else: @@ -96,9 +124,7 @@ class UniversalRunnerMixin: tool_calls = getattr(response, "tool_calls", None) or [] if not tool_calls: - _ex.logger.warning( - f"Universal Explorer iteration {iterations}: No tool calls generated." - ) + _ex.logger.warning(f"Universal Explorer turn {turn}: No tool calls generated.") if is_final_turn: return json.dumps( { @@ -109,27 +135,19 @@ class UniversalRunnerMixin: ) continue - # Check for submit_answer submit_outcome = self._universal_submit_outcome(tool_calls) if submit_outcome is not None: - agent_outcome = submit_outcome - return agent_outcome + return submit_outcome - # Execute non-submit tools - await self._universal_dispatch_tools(tool_calls, messages, iterations) + await self._universal_dispatch_tools(tool_calls, messages, turn) - if not agent_outcome: - agent_outcome = json.dumps( - { - "candidates": [], - "fallback_message": ( - "Explorer reached max iterations without finding candidates." - ), - }, - ensure_ascii=False, - ) - - return agent_outcome + return json.dumps( + { + "candidates": [], + "fallback_message": "Explorer reached max iterations without finding candidates.", + }, + ensure_ascii=False, + ) def _build_universal_messages( self, @@ -167,7 +185,7 @@ class UniversalRunnerMixin: def _universal_submit_outcome(self, tool_calls: list) -> str | None: """Returns the validated submit_answer outcome JSON, or None if absent.""" - submit_call = next((tc for tc in tool_calls if tc.get("name") == "submit_answer"), None) + submit_call = next((tc for tc in tool_calls if tc.get("name") == SUBMIT_TOOL), None) if not submit_call: return None @@ -175,36 +193,102 @@ class UniversalRunnerMixin: candidates = args.get("candidates", []) fallback_message = args.get("fallback_message", "") - # Validate coordinates - valid_candidates = [] - for cand in candidates: - if isinstance(cand, dict) and "coords" in cand: - coords = cand["coords"] - if isinstance(coords, list) and len(coords) == 2: - try: - nx, ny = int(coords[0]), int(coords[1]) - if 0 <= nx <= 1000 and 0 <= ny <= 1000: - valid_candidates.append(cand) - except (ValueError, TypeError): - pass + valid_candidates = self._enrich_candidates( + [ + cand + for cand in candidates + if isinstance(cand, dict) and is_valid_norm_point(cand.get("coords")) + ] + ) return json.dumps( - { - "candidates": valid_candidates, - "fallback_message": fallback_message, - }, + {"candidates": valid_candidates, "fallback_message": fallback_message}, ensure_ascii=False, ) - async def _universal_dispatch_tools( - self, tool_calls: list, messages: list[BaseMessage], iterations: int - ) -> None: - """Executes non-submit tool calls and appends their ToolMessages.""" + async def _universal_execute_tool(self, name: str, args: dict) -> dict[str, Any]: + """Runs one perception tool by name; unknown names yield an error result.""" + if name == "ask_perception_tool": + return await self.exec_ask_perception_tool( + search_query=args.get("search_query"), + nx=args.get("nx"), + ny=args.get("ny"), + detect_queries=args.get("detect_queries"), + ) + if name == "detect_objects": + return await self.exec_detect_objects( + queries=args.get("queries"), + target_image_id=args.get("target_image_id", "img_0"), + ) + if name == "get_ocr_list": + return await self.exec_get_ocr_list() + if name == "ask_image_processor": + return await self.exec_ask_image_processor( + instruction=args.get("instruction"), + target_image_id=args.get("target_image_id", "img_0"), + ) + if name == "inspect_region": + return await self.exec_inspect_region( + x_min=args.get("x_min"), + y_min=args.get("y_min"), + x_max=args.get("x_max"), + y_max=args.get("y_max"), + zoom_factor=args.get("zoom_factor", 2.0), + ) + return {"text": f"Error: Tool '{name}' is not recognized."} + + @staticmethod + def _tool_image_paths(res: Any) -> list[str]: + """Collects the annotated image paths a tool result may carry.""" + if not isinstance(res, dict): + return [] + paths: list[str] = [] + single = res.get("image_path") + if single: + paths.append(str(single)) + paths.extend(str(p) for p in (res.get("image_paths") or []) if p) + return paths + + def _build_tool_images_message(self, images: list[tuple[str, str]]) -> HumanMessage | None: + """One HumanMessage carrying every annotated image produced this turn. + + LangChain ``ToolMessage`` content is text-only for most providers, so + the images ride on a follow-up human turn instead; unreadable files + are skipped so a missing annotation never aborts the loop. + """ _ex = facade() + blocks: list[dict[str, Any]] = [] + tool_names: list[str] = [] + for tool_name, path in images: + try: + with open(path, "rb") as f: + img_b64 = base64.b64encode(f.read()).decode("utf-8") + except OSError as read_err: + _ex.logger.warning(f"Skipping unreadable tool image {path}: {read_err}") + continue + if tool_name not in tool_names: + tool_names.append(tool_name) + blocks.append( + {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}} + ) + if not blocks: + return None + header = { + "type": "text", + "text": f"[Annotated image(s) returned by: {', '.join(tool_names)}]", + } + return HumanMessage(content=[header, *blocks]) + + async def _universal_dispatch_tools( + self, tool_calls: list, messages: list[BaseMessage], turn: int + ) -> None: + """Executes non-submit tool calls, appending ToolMessages and their images.""" + _ex = facade() + images: list[tuple[str, str]] = [] for tc in tool_calls: name = tc.get("name") args = tc.get("args", {}) - call_id = tc.get("id", f"call_{iterations}_{name}") + call_id = tc.get("id", f"call_{turn}_{name}") if name in self.denylisted_tools: messages.append( @@ -217,39 +301,10 @@ class UniversalRunnerMixin: continue try: - if name == "ask_perception_tool": - res = await self.exec_ask_perception_tool( - search_query=args.get("search_query"), - nx=args.get("nx"), - ny=args.get("ny"), - detect_queries=args.get("detect_queries"), - ) - elif name == "detect_objects": - res = await self.exec_detect_objects( - queries=args.get("queries"), - target_image_id=args.get("target_image_id", "img_0"), - ) - elif name == "get_ocr_list": - res = await self.exec_get_ocr_list() - elif name == "ask_image_processor": - res = await self.exec_ask_image_processor( - instruction=args.get("instruction"), - target_image_id=args.get("target_image_id", "img_0"), - ) - elif name == "inspect_region": - res = await self.exec_inspect_region( - x_min=args.get("x_min"), - y_min=args.get("y_min"), - x_max=args.get("x_max"), - y_max=args.get("y_max"), - zoom_factor=args.get("zoom_factor", 2.0), - ) - else: - res = {"text": f"Error: Tool '{name}' is not recognized."} - + res = await self._universal_execute_tool(name, args) res_text = res.get("text") or res.get("result") or str(res) messages.append(ToolMessage(tool_call_id=call_id, name=name, content=res_text)) - + images.extend((name, path) for path in self._tool_image_paths(res)) except Exception as tool_err: _ex.logger.error(f"Error executing tool '{name}': {tool_err}") messages.append( @@ -259,3 +314,7 @@ class UniversalRunnerMixin: content=f"Error executing tool '{name}': {tool_err}", ) ) + + image_message = self._build_tool_images_message(images) if images else None + if image_message is not None: + messages.append(image_message) diff --git a/artemis/agents/flash/context_compressor.py b/artemis/agents/flash/context_compressor.py index bb293d4..78f6973 100644 --- a/artemis/agents/flash/context_compressor.py +++ b/artemis/agents/flash/context_compressor.py @@ -145,9 +145,7 @@ class ScrubEdgeCompressor: continue if block.get("type") in ("image_url", "image"): has_image = True - elif block.get("type") == "text" and self._has_strip_marker( - block.get("text", "") - ): + elif block.get("type") == "text" and self._has_strip_marker(block.get("text", "")): has_xml = True if has_xml: @@ -186,9 +184,7 @@ class ScrubEdgeCompressor: # With a live tail outside the tracked list (tail_offset > 0) the tail # itself occupies the newest slots of the keep window. keep_count = max(0, self._xml_scrub_depth - self._tail_offset) - keep_indices = ( - {rec["idx"] for rec in self._tracked[-keep_count:]} if keep_count else set() - ) + keep_indices = {rec["idx"] for rec in self._tracked[-keep_count:]} if keep_count else set() still_pending: list[int] = [] for idx in self._xml_candidates: if idx in keep_indices: @@ -210,9 +206,7 @@ class ScrubEdgeCompressor: and block.get("type") == "text" and self._has_strip_marker(block.get("text", "")) ): - retained_text = _without_marked_suffix( - block.get("text", ""), self._strip_markers - ) + retained_text = _without_marked_suffix(block.get("text", ""), self._strip_markers) if retained_text: retained_block = dict(block) retained_block["text"] = retained_text @@ -275,11 +269,7 @@ class ScrubEdgeCompressor: return None, False, False, None summary = summarizer.get_summary(effective_key) - if ( - summary is None - and not summarizer.has_job(effective_key) - and rec["legacy"] is not None - ): + if summary is None and not summarizer.has_job(effective_key) and rec["legacy"] is not None: effective_key = rec["legacy"] summary = summarizer.get_summary(effective_key) @@ -327,9 +317,7 @@ class ScrubEdgeCompressor: # Defensive fallback: never leave ToolMessage or HumanMessage content empty if not new_blocks: - new_blocks = [ - {"type": "text", "text": "Action completed." if rec["is_tool"] else ""} - ] + new_blocks = [{"type": "text", "text": "Action completed." if rec["is_tool"] else ""}] msg.content = new_blocks self._frozen.add(rec["idx"]) diff --git a/artemis/agents/flash/flash_runner.md b/artemis/agents/flash/flash_runner.md index 81832fb..33cee9e 100644 --- a/artemis/agents/flash/flash_runner.md +++ b/artemis/agents/flash/flash_runner.md @@ -16,26 +16,33 @@ You operate in a dynamic observation-reasoning-execution loop. **CRITICAL RULE: Keep in mind that the mobile device is an asynchronous, constantly evolving environment. However, your actions entail an inherent "turn latency" (a 3–7 second delay caused by model inference and network transmission between normal consecutive actions). - **State Drift & Progression Tolerance**: While you think, video playback advances, banners scroll, and web pages finish loading. If the latest observation has progressed further than expected (e.g., an ad automatically skipped or a redirect landed), **do not treat it as an error**. Adapt smoothly to the live screen in front of you. {% if "wait_for_delay" in available_tools %}- **Timed Waiting & Transitions**: When facing mid-transition animations, loading spinners, required watch durations, or ad countdown timers, use the precise `wait_for_delay` command to allow time to pass or the UI state to stabilize. Some transition states are subtle and may even resemble a phone bug or system freeze; it is always advisable to wait first, and only consider retrying or choosing an alternative path if the state remains broken.{% endif %} -- **Time-Sensitive Tasks & Transient UI**: First, you must understand that each of your decision turns takes several seconds of model inference and network delay, while the mobile device state is continuously changing. A target element that you see on the current screenshot (e.g., auto-dismissing overlays, temporary message bars, menus or control bars with short timeouts) may have already vanished by the time your physical click command reaches the device! This is one of the most common reasons why the UI does not change as expected after a single step. We call these **Time-Sensitive Tasks**. +- **Time-Sensitive Tasks & Transient UI**: Because of the turn latency above, a target element that you see on the current screenshot (e.g., auto-dismissing overlays, temporary message bars, menus or control bars with short timeouts) may have already vanished by the time your physical click command reaches the device! This is one of the most common reasons why the UI does not change as expected after a single step. We call these **Time-Sensitive Tasks**. - *Mental Model*: When facing these tasks, any attempt to interact step-by-step (such as tapping once to wake up a menu/bar and waiting for the next turn to tap the target button) WILL fail because the element will auto-dismiss during your decision delay. You MUST combine the summon action on the trigger element (`Trigger`) and the target action (`Target`) into a single atomic sequence. - *Chained Execution*: Use `click_sequence(sequence=[[trigger_x, trigger_y], [target_x, target_y]])` in `0-1000` normalized scale to execute both taps consecutively with a 50ms delay, catching the transient element before the timeout closes it. --- -# 3. STAGNATION & LOOP PREVENTION -- **Retrying is not the only option.**: If you perform the same action on the same target for two consecutive turns and the screen or XML shows **no substantial change**, further retries are unlikely to be useful; the probability of the execution tool itself malfunctioning is very low, so you should pause and consider the underlying reason. +# 3. READING YOUR HISTORY & THE SESSION CLOCK +- **Conversation = execution history**: The earlier turns of this conversation are your raw execution history. Each observation opens with `# CURRENT OBSERVATION [T+mm:ss]`, and each turn that executed a device action ends with an `--- Action Execution Result (T+mm:ss) ---` message (`Status: success` means the action was dispatched and observed; `Status: failed` carries the `Error:` line). Every timestamp is a session-relative `T+mm:ss` offset from the start of this session; it is the only clock you have, for pacing waits as well as for reasoning about elapsed time. +- **Compression**: Screenshots older than a few turns are replaced by short `--- Historical Visual Transition ---` summaries and old `--- Visible UI Elements ---` lists are dropped. Long spans render as `[Era N | Steps a–b | T+.. → T+..]` blocks (a merged synopsis, segment titles, and a per-step action ledger). Because of this, if you spot critical text that you will need later (e.g., verification codes, specific titles, search lists), **you must explicitly verbalize and write those details into your text reasoning** so they persist across turns. +{% if "recall_history" in available_tools %}- **Cold-History Recall (`recall_history`)**: Compressed history keeps the step numbers and `T+` offsets but not every detail. When you need exactly what you did or saw at an earlier step (a value you read, the result of an action, which screen you were on), call `recall_history` with keywords and/or the `[start, end]` step range shown in a chunk block; it searches the full stored history (summaries, actions, results, reasoning, on-screen text). It is a pre-decision helper: read its answer, then act.{% endif %} +{% if "video_analyzer" in available_tools %}- **Video Recall (`video_analyzer`)**: The whole session is continuously screen-recorded on the same `T+` clock. When a static screenshot is not enough — a video or animation that played, something that changed between two observations, a transient toast, a fast-scrolling list — call `video_analyzer` with a time range expressed on that clock (e.g., `from T+01:20 to T+01:35`, or `from 80s to 95s`) and a precise question. Prefer narrow ranges; ask for audio explicitly when only audio matters. It is a pre-decision helper: read its answer, then act.{% endif %} + +--- + +# 4. STAGNATION & LOOP PREVENTION +- **Retrying is not the only option.**: If you perform the same action on the same target for two consecutive turns and the screen or the UI element list shows **no substantial change**, further retries are unlikely to be useful; the probability of the execution tool itself malfunctioning is very low, so you should pause and consider the underlying reason. - **Flexibly Adjust Strategies:** Always prioritize resolving the immediate issue. If you encounter overlays or pop-ups blocking input, or if an element is unresponsive, actively consider alternative ways to complete the task. If an action fails to proceed as expected, systematically consider whether the issue lies with the device itself, the operational path, or a change in state occurring between your actual interaction and the screenshot (Time-Sensitive Tasks). --- -# 4. ACTION & MEMORY RULES +# 5. ACTION RULES 1. **Normalized Coordinates (`0-1000` Scale)**: Always use normalized `[x, y]` coordinates in `0-1000` scale for any location-based target (e.g., `target=[500, 600]` or `click_sequence=[[300, 400], [500, 600]]`). -2. **Do not determine element coordinates based on guesswork.** If the element is visible in the screenshot but its coordinates are missing from the provided XML tree, please use the `ask_explorer` tool to locate the element first. -3. **Screenshot Pruning Awareness**: To optimize speed, the system **prunes old intermediate screenshots from your history**—you only ever see the *latest* live screenshot and UI tree. Therefore, if you spot critical text in an earlier step that you will need later (e.g., verification codes, specific titles, search lists), **you must explicitly verbalize and write those details into your text thinking/notes** so they persist across turns. -{% if "manage_app" in available_tools %}4. **App Launching**: To start or reset an application, always use `manage_app(action="launch", app_name="...")` or `action="stop"`. Do not manually swipe across home screens.{% endif %} +2. **Do not determine element coordinates based on guesswork.** If the element is visible in the screenshot but its coordinates are missing from the provided `--- Visible UI Elements ---` list, please use the `ask_explorer` tool to locate the element first. +{% if "manage_app" in available_tools %}3. **App Launching**: To start or reset an application, always use `manage_app(action="launch", app_name="...")` or `action="stop"`. Do not manually swipe across home screens.{% endif %} --- -# 5. TERMINATION PROTOCOL (`report_task_status`) +# 6. TERMINATION PROTOCOL (`report_task_status`) - **Completion (`status="completed"`)**: ONLY call `report_task_status(status="completed", ...)` after you have **visually verified** from the latest screen that the user's objective (`{{ goal }}`) is fully achieved. - **Failure (`status="failed"`)**: If the task is genuinely impossible due to paywalls, unrecoverable crashes, or hitting an absolute dead end after sensible fallbacks, call `report_task_status(status="failed", ...)` with the exact blocking reason. diff --git a/artemis/agents/flash/runner.py b/artemis/agents/flash/runner.py index 3327851..ce5c1c9 100644 --- a/artemis/agents/flash/runner.py +++ b/artemis/agents/flash/runner.py @@ -16,10 +16,21 @@ Executes autonomous reactive mobile workflows across Google Gemini, OpenAI GPT-4o/o3, Anthropic Claude 3.5/3.7, and OpenRouter endpoints. + +Conversation shape (shared with the Pro operator, history redesign §3.2): +the prompt is built every turn from a session :class:`TranscriptLedger` — +a byte-stable system prefix, the committed earlier turns (append-only; old UI +lists stripped at depth 1, screenshots resolved to visual summaries at depth +K, long spans chunk-compressed), and a fresh tail carrying the current +observation under a ``# CURRENT OBSERVATION [T+mm:ss]`` header. Each +committed turn ends with an ``--- Action Execution Result (T+mm:ss) ---`` +message, so every timestamp the model sees is a session-relative offset — the +same clock the video analyzer uses for the session recording. """ import asyncio import base64 +from dataclasses import dataclass, field import json from pathlib import Path import re @@ -33,7 +44,6 @@ from langchain_core.messages import ( ToolMessage, ) -from artemis.agents.flash.context_compressor import ScrubEdgeCompressor from artemis.agents.flash.summarizer import VisualStepSummarizer from artemis.agents.validator.tool_declarations import ( ASK_EXPLORER_TOOL, @@ -56,6 +66,7 @@ from artemis.graph.perception import _check_injected_instruction_file from artemis.graph.state import State from artemis.llm.structured import ParseFailure, parse_structured from artemis.mcp.action_executor import McpActionExecutor +from artemis.memory.transcript import PRO_UI_LIST_MARKER, TranscriptLedger from artemis.services.llm import ( RobustChatModelWrapper, acomplete, @@ -68,6 +79,47 @@ from artemis.utils.logger import get_logger logger = get_logger(__name__) +#: Reminder carried by the first observation only (the system prompt states the rule). +_REASONING_FIRST_REMINDER = ( + "CRITICAL RULE: In every single turn, you MUST FIRST output a natural" + " language reasoning/explanation paragraph BEFORE invoking any tool call." +) + +_NO_TOOL_CALL_NOTICE = ( + "You did not call any tools last turn. Please make progress by calling an" + " action tool or 'report_task_status'." +) + +_FINAL_TURN_WARNING = "[WARNING] This is your final turn; only 'report_task_status' is available." + + +@dataclass +class _TurnRecord: + """What one reactive turn produced, for committing it into the ledger. + + ``step_keys`` are the DataEngine step ids recorded this turn (one per + executed action; the tool_call_id stands in without a DataEngine) — the + first keys the observation screenshot to its visual-transition summary, + all of them feed the chunk ledger. ``actions`` collects the outcome of + every device action so the turn's execution result can be rendered. + """ + + step_keys: list[str] = field(default_factory=list) + actions: list[tuple[str, str, str]] = field(default_factory=list) + + def result(self) -> dict | None: + """The turn's execution result in the validator-report shape. + + ``None`` when no device action ran (helper-only turns have no result + message, as in Pro). + """ + if not self.actions: + return None + for name, status, text in self.actions: + if status != "success": + return {"status": "failed", "error": f"{name}: {text}"} + return {"status": "success"} + class FlashRunner: """Reactive, ultra-fast agent execution loop supporting all VLM providers.""" @@ -81,14 +133,18 @@ class FlashRunner: self.step_summarizer_cfg = cfg.flash.step_summarizer self.memory_runtime_cfg = cfg.memory.runtime self.transcript_cfg = cfg.memory.transcript + self.chunking_cfg = cfg.memory.chunking except Exception: - self.max_turns = max_turns if max_turns is not None else 30 + self.max_turns = max_turns if max_turns is not None else 0 self.step_summarizer_cfg = StepSummarizerConfig() self.memory_runtime_cfg = MemoryRuntimeConfig() self.transcript_cfg = MemoryTranscriptConfig() + self.chunking_cfg = None self.controller = UnifiedMobileController(ctx) - self.executor = McpActionExecutor(ctx, self.controller) + # ``agent_name="flash"`` makes ask_explorer follow ``explorer.flash_mode`` + # (the Flash profile knob) instead of the Pro profile's tier. + self.executor = McpActionExecutor(ctx, self.controller, agent_name="flash") self.summarizer = ( VisualStepSummarizer( ctx, @@ -105,13 +161,46 @@ class FlashRunner: if self.summarizer is not None and getattr(ctx, "step_memory", None) is None: try: ctx.step_memory = self.summarizer - except Exception: + except (AttributeError, TypeError, ValueError): pass + @property + def turn_limit(self) -> int | None: + """The reactive turn cap, or ``None`` when the loop is unbounded.""" + try: + limit = int(self.max_turns) if self.max_turns is not None else 0 + except (TypeError, ValueError): + return None + return limit if limit > 0 else None + + def _video_tools_enabled(self) -> bool: + """Same gate the Pro graph applies before binding ``video_analyzer``.""" + setup = getattr(self.ctx, "execution_setup", None) + return getattr(setup, "video_recording_tools_enabled", False) is True + + def _recall_enabled(self) -> bool: + """Same gate as the Pro operator: a DataEngine session and the recall config.""" + from artemis.tools.history_recall import _recall_available + + try: + return bool(_recall_available(self.ctx)) + except Exception: + return False + def _get_tools(self) -> list: - tools = [t for t in VALIDATOR_TOOLS_DECLARATION if t.name != "report_failure_analysis"] + tools = list(VALIDATOR_TOOLS_DECLARATION) tools.insert(1, CLICK_SEQUENCE_TOOL) tools.append(ASK_EXPLORER_TOOL) + # Helper tools shared with the Pro operator, declared next to their + # LangChain tools so each contract has one owner. + if self._recall_enabled(): + from artemis.tools.history_recall import RECALL_HISTORY_TOOL + + tools.append(RECALL_HISTORY_TOOL) + if self._video_tools_enabled(): + from artemis.tools.video_tool import VIDEO_ANALYZER_TOOL + + tools.append(VIDEO_ANALYZER_TOOL) tools.append(REPORT_TASK_STATUS_TOOL) # With an actuator installed, drop declarations for device actions the # backend does not implement (and append its extension tools); without @@ -131,19 +220,46 @@ class FlashRunner: # run() setup helpers # ------------------------------------------------------------------ - def _build_compressor(self) -> ScrubEdgeCompressor: - """Builds the per-run scrub-edge compressor. + def _build_ledger(self) -> TranscriptLedger: + """Builds the session transcript ledger with the shared Pro history policy. - Fresh scrub-edge ledger per run: the frozen/watermark bookkeeping is - execution state bound to this run's append-only message list. + The ``T+mm:ss`` clock is anchored to the DataEngine session start so + the observation headers, the chunk ledger lines and the video + analyzer's action timeline share one origin. L2/L3 chunk compression + needs DataEngine step records; without an engine the ledger runs + scrub-edge-only. """ - return ScrubEdgeCompressor( - summarizer=self.summarizer, + engine = getattr(self.ctx, "data_engine", None) + session_start = getattr(engine, "session_start_time", None) if engine else None + cfg = self.transcript_cfg + ledger = TranscriptLedger( + step_memory=self.summarizer, prune_history_xml=self.step_summarizer_cfg.prune_history_xml, - image_scrub_depth=self.transcript_cfg.image_scrub_depth, - pending_grace_steps=self.transcript_cfg.pending_grace_steps, - xml_scrub_depth=self.transcript_cfg.xml_scrub_depth, + image_scrub_depth=getattr(cfg, "image_scrub_depth", 3), + pending_grace_steps=getattr(cfg, "pending_grace_steps", 3), + xml_scrub_depth=getattr(cfg, "xml_scrub_depth", 1), + session_start=session_start if isinstance(session_start, (int, float)) else None, ) + if engine is not None: + try: + from artemis.memory import HistoryChunkManager + + ledger.attach_chunker( + HistoryChunkManager( + engine=engine, + ctx=self.ctx, + chunking_config=self.chunking_cfg, + transcript_config=cfg, + goal=self.goal, + ) + ) + except Exception as e: + logger.error(f"History chunk manager unavailable for FlashRunner: {e}") + try: + self.ctx.transcript_ledger = ledger + except (AttributeError, TypeError, ValueError): + pass + return ledger def _init_llm(self): """Initializes the Universal LLM via the Service Layer.""" @@ -154,37 +270,6 @@ class FlashRunner: return RobustChatModelWrapper(get_google_llm(model_name="gemini-2.5-flash"), self.ctx) - def _build_initial_user_content(self, img_bytes, xml_list) -> list[dict]: - """Builds the first HumanMessage content (goal + screenshot + UI tree).""" - user_content: list[dict] = [ - {"type": "text", "text": f"Your objective is: {self.goal}"}, - ] - if img_bytes: - img_b64 = base64.b64encode(img_bytes).decode("utf-8") - user_content.append( - { - "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}, - } - ) - if xml_list: - user_content.append( - { - "type": "text", - "text": f"--- UI Element List ---\n{xml_list}", - } - ) - user_content.append( - { - "type": "text", - "text": ( - "CRITICAL RULE: In every single turn, you MUST FIRST output a natural" - " language reasoning/explanation paragraph BEFORE invoking any tool call." - ), - } - ) - return user_content - def _render_system_prompt(self, tools_declaration: list) -> str: """Renders the system prompt from the flash_runner.md template. @@ -195,62 +280,83 @@ class FlashRunner: prompt_path = Path(__file__).parent / "flash_runner.md" prompt_template = prompt_path.read_text(encoding="utf-8") available_tools = frozenset(t.name for t in tools_declaration) - return Template(prompt_template).render( - goal=self.goal, available_tools=available_tools - ) + return Template(prompt_template).render(goal=self.goal, available_tools=available_tools) # ------------------------------------------------------------------ # Per-turn helpers (observe / think) # ------------------------------------------------------------------ - async def _append_injected_instruction(self, messages: list[BaseMessage]) -> None: - """Checks for real-time injected instructions and appends them.""" - if self.ctx.data_engine and self.ctx.data_engine.base_dir: - try: - injected_payload = await asyncio.to_thread( - _check_injected_instruction_file, - str(self.ctx.data_engine.base_dir), - ) - if injected_payload and injected_payload.get("instruction"): - injected_text = ( - "[REAL-TIME INJECTED INSTRUCTION from user]:" - f" {injected_payload['instruction']}\nYou MUST immediately" - " follow this instruction and adjust your plan/actions." - ) - if injected_payload.get("release_loop"): - injected_text += ( - "\nThe user has explicitly authorized stopping any" - " ongoing monitoring loop; you may now wrap up and" - " complete the task." - ) - messages.append(HumanMessage(content=injected_text)) - except Exception as e: - logger.warning(f"Failed to check injected instruction in FlashRunner: {e}") - - def _select_turn_tools( - self, turns: int, tools_declaration: list, messages: list[BaseMessage] - ) -> list: - """Applies the tool restriction on the final turn.""" - if turns == self.max_turns: - messages.append( - HumanMessage( - content=( - "[WARNING] This is your final turn; only" - " 'report_task_status' is available." - ) - ) + async def _read_injected_instruction(self) -> str | None: + """Returns the real-time injected instruction text for this turn, if any.""" + if not (self.ctx.data_engine and self.ctx.data_engine.base_dir): + return None + try: + injected_payload = await asyncio.to_thread( + _check_injected_instruction_file, + str(self.ctx.data_engine.base_dir), ) - return [t for t in tools_declaration if t.name == "report_task_status"] - return tools_declaration + except (OSError, ValueError, AttributeError) as e: + logger.warning(f"Failed to check injected instruction in FlashRunner: {e}") + return None + if not (injected_payload and injected_payload.get("instruction")): + return None + injected_text = ( + "[REAL-TIME INJECTED INSTRUCTION from user]:" + f" {injected_payload['instruction']}\nYou MUST immediately" + " follow this instruction and adjust your plan/actions." + ) + if injected_payload.get("release_loop"): + injected_text += ( + "\nThe user has explicitly authorized stopping any" + " ongoing monitoring loop; you may now wrap up and" + " complete the task." + ) + return injected_text + + def _build_tail( + self, + ledger: TranscriptLedger, + turns: int, + img_bytes, + xml_list, + *, + injected: str | None = None, + notices: list[str] | None = None, + is_final: bool = False, + ) -> HumanMessage: + """Builds this turn's observation tail (Pro observation shape). + + Header, screenshot and UI list carry the same markers as the Pro + operator's tail so the ledger's scrub edge treats both alike. + """ + blocks: list[dict] = [] + if turns == 1: + blocks.append({"type": "text", "text": f"Your objective is: {self.goal}"}) + blocks.append({"type": "text", "text": f"# CURRENT OBSERVATION [{ledger.elapsed_label()}]"}) + if img_bytes: + img_b64 = base64.b64encode(img_bytes).decode("utf-8") + blocks.append({"type": "text", "text": "--- Current Screenshot ---"}) + blocks.append( + {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{img_b64}"}} + ) + if xml_list: + blocks.append({"type": "text", "text": f"{PRO_UI_LIST_MARKER}\n{xml_list}"}) + for notice in notices or []: + blocks.append({"type": "text", "text": notice}) + if injected: + blocks.append({"type": "text", "text": injected}) + if is_final: + blocks.append({"type": "text", "text": _FINAL_TURN_WARNING}) + if turns == 1: + blocks.append({"type": "text", "text": _REASONING_FIRST_REMINDER}) + return HumanMessage(content=blocks) def _extract_response_text(self, response) -> str: """Extracts the natural-language thought text from the model response.""" raw_text = response.content if isinstance(response.content, str) else "" if isinstance(response.content, list): raw_text = "".join( - b.get("text", "") - for b in response.content - if isinstance(b, dict) and "text" in b + b.get("text", "") for b in response.content if isinstance(b, dict) and "text" in b ) return raw_text @@ -271,9 +377,9 @@ class FlashRunner: elif hasattr(response, "response_metadata") and isinstance( response.response_metadata, dict ): - u = response.response_metadata.get( - "usage_metadata" - ) or response.response_metadata.get("token_usage") + u = response.response_metadata.get("usage_metadata") or response.response_metadata.get( + "token_usage" + ) if isinstance(u, dict): pr = ( u.get("input_tokens") @@ -321,9 +427,7 @@ class FlashRunner: "total_tokens": max(1, prompt_tokens + completion_tokens), } - def _resolve_token_usage( - self, response, messages: list[BaseMessage], raw_text: str - ) -> dict: + def _resolve_token_usage(self, response, messages: list[BaseMessage], raw_text: str) -> dict: """Extracts token usage from response metadata, estimating as fallback.""" step_token_usage = self._token_usage_from_response(response) if not step_token_usage or step_token_usage.get("total_tokens", 0) <= 0: @@ -390,9 +494,7 @@ class FlashRunner: ui_tree=xml_list, action_taken={"action": "report_task_status", "args": args}, operator_raw_thinking=raw_text, - last_execution_result={ - "result": "Task completed with final report." - }, + last_execution_result={"result": "Task completed with final report."}, extra_metadata={"token_usage": step_token_usage}, ) except Exception as step_err: @@ -420,9 +522,7 @@ class FlashRunner: if not exec_result.ui_elements_text: exec_result.ui_elements_text = screen_data.elements except Exception as shot_err: - logger.warning( - f"Failed to capture fallback screenshot in FlashRunner: {shot_err}" - ) + logger.warning(f"Failed to capture fallback screenshot in FlashRunner: {shot_err}") return post_img_bytes def _extract_normalized_coordinates(self, name: str, args: dict): @@ -432,11 +532,7 @@ class FlashRunner: norm_end = None if name == "swipe": kind, target_val, _ = parse_swipe_parameters(args) - if ( - kind == "coords" - and isinstance(target_val, list) - and len(target_val) == 4 - ): + if kind == "coords" and isinstance(target_val, list) and len(target_val) == 4: norm_coords = target_val norm_start = target_val[:2] norm_end = target_val[2:] @@ -473,6 +569,7 @@ class FlashRunner: pre_screenshot_bytes, xml_list, post_img_bytes, + injected: str | None = None, ): """Records telemetry / step in DataEngine; returns the step id or None.""" recorded_step_id = None @@ -480,9 +577,7 @@ class FlashRunner: if self.ctx.data_engine.current_step_id is None: self.ctx.data_engine.allocate_step_id() - norm_coords, norm_start, norm_end = self._extract_normalized_coordinates( - name, args - ) + norm_coords, norm_start, norm_end = self._extract_normalized_coordinates(name, args) action_dict = { "action": name, @@ -503,25 +598,49 @@ class FlashRunner: # Record-time enrichment computed by the executor from # the pre-action frame (target_text / target_class / # target_resource_id / target_label_source). - target_semantics = (exec_result.metadata or {}).get( - "target_semantics" - ) + target_semantics = (exec_result.metadata or {}).get("target_semantics") if isinstance(target_semantics, dict): action_dict.update(target_semantics) + succeeded = exec_result.status == "success" + last_execution_result = { + "status": "success" if succeeded else "failed", + "result": exec_result.text_summary, + } + if not succeeded: + last_execution_result["error"] = exec_result.text_summary + + extra_metadata: dict = {"token_usage": step_token_usage} + if injected: + # Stamped verbatim on the step it reached: the chunk ledger + # keeps it as a never-evicted line at every compression level. + extra_metadata["injected_instruction"] = injected + recorded_step_id = self.ctx.data_engine.record_step( pre_screenshot_bytes=pre_screenshot_bytes, post_screenshot_bytes=post_img_bytes, ui_tree=(exec_result.ui_elements_text or xml_list), action_taken=action_dict, operator_raw_thinking=raw_text, - last_execution_result={"result": exec_result.text_summary}, - extra_metadata={"token_usage": step_token_usage}, + last_execution_result=last_execution_result, + extra_metadata=extra_metadata, ) + self._notify_history_chunker(recorded_step_id) except Exception as step_err: logger.warning(f"Error recording step in FlashRunner: {step_err}") return recorded_step_id + def _notify_history_chunker(self, step_id) -> None: + """Stamps the recorded step for the chunk manager (single segment, no plan).""" + ledger = getattr(self.ctx, "transcript_ledger", None) + chunker = getattr(ledger, "chunker", None) if ledger is not None else None + if chunker is None or step_id is None: + return + try: + chunker.on_step_stamped(str(step_id), None) + except Exception as e: + logger.warning(f"History chunker step stamp failed: {e}") + async def _execute_and_record_action( self, name: str, @@ -534,22 +653,23 @@ class FlashRunner: pre_screenshot_bytes, xml_list, action_sequence: int, + turn: _TurnRecord, + injected: str | None = None, ): - """Executes one action tool call, records it, and updates loop state. + """Executes one tool call, records it, and updates loop state. + The tool message carries the outcome text only; the post-action + screenshot and UI list become the next turn's observation tail. Returns the (possibly updated) pre_screenshot_bytes, xml_list, and action_sequence for the next iteration. """ + # Dynamic dispatch set: manifest device actions plus any backend + # extension tools, so extension steps are recorded like actions. + action_names = self.executor.action_tool_names try: exec_result = await self.executor.execute(name, args, tc_id, state) - # Dynamic dispatch set: manifest device actions plus any backend - # extension tools, so extension steps are recorded like actions. - action_names = self.executor.action_tool_names - - post_img_bytes = await self._capture_post_screenshot( - exec_result, name, action_names - ) + post_img_bytes = await self._capture_post_screenshot(exec_result, name, action_names) # Record telemetry / step in DataEngine recorded_step_id = None @@ -563,8 +683,13 @@ class FlashRunner: pre_screenshot_bytes, xml_list, post_img_bytes, + injected=injected, ) + if name in action_names: + turn.step_keys.append(str(recorded_step_id) if recorded_step_id else str(tc_id)) + turn.actions.append((name, exec_result.status, exec_result.text_summary)) + # ⚡ Non-blocking dispatch of objective visual transition summarizer if self.summarizer and name in action_names: action_sequence += 1 @@ -584,10 +709,29 @@ class FlashRunner: if exec_result.ui_elements_text: xml_list = exec_result.ui_elements_text - messages.append(exec_result.to_langchain_tool_message()) + # Helper tools may return multimodal blocks (recalled screenshots); + # device actions always report text — their post screenshot is the + # next observation tail, never a tool-message image. + raw_blocks = exec_result.raw_result if name not in action_names else None + content = ( + raw_blocks + if isinstance(raw_blocks, list) and raw_blocks + else exec_result.text_summary or f"Action '{name}' completed." + ) + messages.append( + ToolMessage( + tool_call_id=tc_id, + name=name, + content=content, + status=exec_result.status, + ) + ) except Exception as e: logger.error(f"Error executing tool {name}: {e}") + if name in action_names: + turn.step_keys.append(str(tc_id)) + turn.actions.append((name, "error", f"Error executing tool {name}: {e}")) messages.append( ToolMessage( tool_call_id=tc_id, @@ -620,6 +764,8 @@ class FlashRunner: pre_screenshot_bytes, xml_list, action_sequence: int, + turn: _TurnRecord, + injected: str | None = None, ): """Dispatches the turn's tool calls. @@ -634,71 +780,113 @@ class FlashRunner: if name == "report_task_status": final_report = await self._finalize_task_report( - name, args, tc_id, raw_text, step_token_usage, - pre_screenshot_bytes, xml_list, messages, + name, + args, + tc_id, + raw_text, + step_token_usage, + pre_screenshot_bytes, + xml_list, + messages, ) return final_report, pre_screenshot_bytes, xml_list, action_sequence - pre_screenshot_bytes, xml_list, action_sequence = ( - await self._execute_and_record_action( - name, args, tc_id, state, messages, raw_text, step_token_usage, - pre_screenshot_bytes, xml_list, action_sequence, - ) + pre_screenshot_bytes, xml_list, action_sequence = await self._execute_and_record_action( + name, + args, + tc_id, + state, + messages, + raw_text, + step_token_usage, + pre_screenshot_bytes, + xml_list, + action_sequence, + turn, + injected=injected, ) return None, pre_screenshot_bytes, xml_list, action_sequence async def _prepare_conversation(self, state: State, tools_declaration: list): - """Captures the initial device state and builds the seed messages.""" - # 2. Capture Initial State (Screenshot + UI Tree) + """Installs the static prefix and captures the initial device state.""" + ledger = self._build_ledger() + ledger.set_static_prefix( + [SystemMessage(content=self._render_system_prompt(tools_declaration))] + ) + + # Capture Initial State (Screenshot + UI Tree) shot_path, img_bytes, xml_list = await capture_screenshot_and_parse_ui( self.ctx, state, self.controller, skip_settling=False ) state.latest_screenshot = shot_path + return ledger, img_bytes, xml_list - messages: list[BaseMessage] = [ - SystemMessage(content=self._render_system_prompt(tools_declaration)), - HumanMessage(content=self._build_initial_user_content(img_bytes, xml_list)), - ] - return messages, img_bytes, xml_list + @staticmethod + def _commit_turn(ledger: TranscriptLedger, turn: _TurnRecord | None) -> None: + """Commits the previous turn once its step ids and outcomes are known.""" + if turn is None: + return + ledger.commit_staged( + step_key=turn.step_keys[0] if turn.step_keys else None, + validator_result=turn.result(), + extra_step_keys=turn.step_keys[1:], + ) @trace(type="agent", name="FlashRunner") async def run(self, state: State) -> dict: - logger.info(f"Starting Artemis Flash reactive loop for goal: {self.goal}") - - compressor = self._build_compressor() + limit = self.turn_limit + logger.info( + f"Starting Artemis Flash reactive loop for goal: {self.goal}" + f" (turn limit: {limit if limit else 'unlimited'})" + ) # 1. Initialize Universal LLM via Service Layer llm = self._init_llm() tools_declaration = self._get_tools() + report_only_tools = [t for t in tools_declaration if t.name == "report_task_status"] - messages, img_bytes, xml_list = await self._prepare_conversation( - state, tools_declaration - ) + ledger, img_bytes, xml_list = await self._prepare_conversation(state, tools_declaration) turns = 0 action_sequence = 0 final_report = None current_pre_screenshot_bytes = img_bytes current_xml_list = xml_list + previous_turn: _TurnRecord | None = None + pending_notices: list[str] = [] - while turns < self.max_turns: + while limit is None or turns < limit: turns += 1 - logger.info(f"--- Artemis Flash Turn {turns}/{self.max_turns} ---") + logger.info(f"--- Artemis Flash Turn {turns}{f'/{limit}' if limit else ''} ---") if self.ctx.data_engine: self.ctx.data_engine.allocate_step_id() + # Commit the previous turn: its step ids and outcomes exist now. + self._commit_turn(ledger, previous_turn) + previous_turn = None + # Check for real-time injected instructions - await self._append_injected_instruction(messages) + injected = await self._read_injected_instruction() - # Advance the scrub edge even when visual summarization is - # disabled, so screenshot and historical XML pruning keep one - # code path. Messages behind the edge are frozen permanently. - compressor.compress(messages) + # Tool restriction on the final turn (bounded loops only) + is_final = limit is not None and turns == limit + tail = self._build_tail( + ledger, + turns, + current_pre_screenshot_bytes, + current_xml_list, + injected=injected, + notices=pending_notices, + is_final=is_final, + ) + pending_notices = [] - # Tool restriction on the final turn - current_tools = self._select_turn_tools(turns, tools_declaration, messages) + # S + F + A + tail: chunk compression and the scrub edge advance here. + messages = ledger.render([tail]) + turn_base = len(messages) - 1 + current_tools = report_only_tools if is_final else tools_declaration response = await self._invoke_model(llm, current_tools, messages) @@ -722,36 +910,46 @@ class FlashRunner: f"FlashRunner received response without tool calls at turn {turns}:" f" {raw_text[:100]}..." ) - if turns == self.max_turns: + if is_final: final_report = {"status": "failed", "explanation": raw_text} break - messages.append( - HumanMessage( - content=( - "You did not call any tools. Please make progress" - " by calling an action tool or 'report_task_status'." - ) - ) - ) + pending_notices.append(_NO_TOOL_CALL_NOTICE) + ledger.stage_turn(messages[turn_base:]) + previous_turn = _TurnRecord() continue # Process tool calls + turn = _TurnRecord() ( final_report_from_calls, current_pre_screenshot_bytes, current_xml_list, action_sequence, ) = await self._process_tool_calls( - tool_calls, state, messages, raw_text, step_token_usage, - current_pre_screenshot_bytes, current_xml_list, action_sequence, + tool_calls, + state, + messages, + raw_text, + step_token_usage, + current_pre_screenshot_bytes, + current_xml_list, + action_sequence, + turn, + injected=injected, ) + ledger.stage_turn(messages[turn_base:]) + previous_turn = turn if final_report_from_calls is not None: return final_report_from_calls if self.summarizer: await self.summarizer.flush() - return final_report or { - "status": "failed", - "explanation": "Max turns reached without final status report.", - } + if final_report is None: + explanation = ( + "Max turns reached without final status report." + if limit is not None and turns >= limit + else "The model returned no response; the reactive loop stopped." + ) + final_report = {"status": "failed", "explanation": explanation} + return final_report diff --git a/artemis/agents/flash/summarizer.py b/artemis/agents/flash/summarizer.py index b6820f6..9d54942 100644 --- a/artemis/agents/flash/summarizer.py +++ b/artemis/agents/flash/summarizer.py @@ -98,13 +98,16 @@ class VisualStepSummarizer(StepMemoryService): except Exception: self._llm = get_google_llm(model_name=target_model, temperature=0.0) try: - configured = getattr(self._llm, "model", None) or getattr( - self._llm, "model_name", None - ) + configured = getattr(self._llm, "model", None) or getattr(self._llm, "model_name", None) if isinstance(configured, str) and configured: self._model_name = configured - except Exception: - pass + except Exception as exc: + logger.debug( + "Could not read the configured summarizer model name; keeping %s: %s", + self._model_name, + exc, + exc_info=True, + ) # Load system prompt templates: the dual-frame transition prompt and # the single-frame variant (§5 revision: describe whichever frames diff --git a/artemis/agents/operator/operator.json b/artemis/agents/operator/operator.json index 907828c..25dbcce 100644 --- a/artemis/agents/operator/operator.json +++ b/artemis/agents/operator/operator.json @@ -1,3 +1,3 @@ { - "main_template": "# ROLE\nYou are the Android Device Operator. Your priority is execution accuracy. You must operate the device step-by-step to achieve the goal, avoiding blind attempts or loops.\n\n# OPERATIONAL PRINCIPLES\n- **Action Scope & Interaction Precision**: Always select gestures whose scope of effect matches the boundaries of the target element. Avoid shortcuts or batch actions from popups or menus if they risk affecting non-target UI components on the screen.[% if \"swipe\" in available_tools %] For any task requiring continuous range interactions, sliding, or boundary selection, prefer using targeted swipe/drag gestures spanning from the element's start bounds [xmin, ymin] to end bounds [xmax, ymax] to ensure the action is strictly confined to the target element. For drag-and-drop, list reordering, or sliding sliders/SeekBars, set duration >= 1000 (e.g. 1500) and always drag slightly PAST the target position to overcome touch slop and reliably trigger the drop/snap/value update. When setting a slider to Maximum (100%) or Minimum (0%), always swipe fully to the extreme boundary to guarantee reaching the absolute limits.[% endif %]\n- **Vision-First Priority**: The screenshot is the primary source of truth for the real device state. Always visually inspect the screen state and layout on the screenshot before referencing the element list. Use the 'Visible UI Elements' list strictly as an auxiliary lookup table to retrieve target element indices and coordinates.\n- **Tool Calling Contract**: Tools that return information needed for the decision ([[ tool_enum(pre_decision_tools, \"and\") ]]) are result-dependent Pre-Decision Tools. Do not submit them in the same tool-call list as a Turn-Ending Action; inspect their result first. The write-through tools `update_note` and `append_note` may accompany at most one Turn-Ending Action when their content is based entirely on evidence already available before that action. Never write an action's expected outcome as if it had already been observed.\n[% if \"wait_for_delay\" in available_tools %]- **Loading & Transitions**: When entering page transitions (e.g., app launching, web page loading, or progress indicators), use `wait_for_delay` to let the screen settle before executing target actions. Some transition states are difficult to discern, such as a thin progress line at the top of the browser screen during webpage loads. Before questioning whether an error has occurred, you can first adopt a waiting strategy to see how the device state evolves. If the state remains ambiguous after waiting, certain UI components might be malfunctioning or failing to load; in this case, consider retrying the action or pivoting to an alternative path.\n[% endif %]- **Memory Preservation & Paginated Exploration**: Using note tools is entirely zero-cost. Actively use this tool to maintain goals and logs. The note tool is the only reliable way to persist memory. Therefore, when you find information on the screen that might be relevant to the task, even if you are not certain, you should first save the information currently visible on the screen before exploring further. If you have already recorded some information, you can use `append_note` to append the new information to the existing content. The note tool can also help you organize lists or information across multiple screens; you can consolidate all information into notes for unified processing.\n- **Large List & Long Text Exploration Strategy (Zero-Miss, Zero-Duplication Single-Pass Principle)**: When traversing large lists, browsing long text bodies, or scanning continuous content feeds, strictly adhere to the single-pass exploration standard:\n 1. **Unidirectional Anchor-Based Traversal (Zero-Miss)**: Maintain a consistent, unidirectional scan (e.g., uniform downward scrolling). Before each scroll, identify the bottom-most visible item/text segment as your visual anchor and carry that anchor plus the scan direction in short-term memory or the traversal ledger. After the scroll, evaluate this handoff before doing anything unrelated: if the anchor remains visible with new content beyond it, record the new content and continue in the same direction without scrolling back to verify; if the anchor is missing, continuity is uncertain, so perform at most one minimal reverse recovery to re-establish overlap, then resume the original direction. While the traversal's declared exit condition remains unresolved, keep this traversal active and do not advance to another milestone or mark it complete.\n 2. **Boundary-Proven Completion**: A single scroll that reveals no new content is not sufficient evidence that the list is complete. For an exhaustive traversal, stop only when an explicit end-of-list indicator is visible, or when one additional successful swipe in the same direction and within the same list container leaves the same final anchor visible with no new items and no loading state. This boundary probe must remain unidirectional; never reverse direction merely to prove completion. If the task declares an earlier exit condition (for example, find one verified match or process N items), that explicit condition may end the traversal without reaching the list boundary.\n 3. **Memory & Plan-Driven Deduplication (Zero-Duplication)**: Systematically log extracted item names, identifiers, or processing states into notes or memory as you progress. After every screen transition, cross-reference visible items against your recorded ledger and strictly interact only with newly surfaced, unrecorded items to eliminate redundant actions on overlapping elements. Proactively maintain subtasks in `task_plan` (e.g., segmenting by batch, category, or alphabetical ranges like `a-d`), tracking exact paths and progress to prevent backtracking.\n 4. **Single-Pass Trust & Anti-Oscillation (No Over-Verification)**: Treat recorded notes as verified ground truth. Once a segment is traversed with confirmed anchor continuity and logged, consider it permanently resolved. Do NOT scroll back-and-forth or perform redundant re-scans for \"double-checking\", even if older execution history has been pruned. Resume from the last recorded anchor and direction. Only the single bounded recovery above, or a refreshed/mutated list, permits limited re-verification; otherwise continue the single pass over new content.\n- **Dynamic Monitoring & Reactive Loop Protocol (Monitor-Trigger-Action)**: When executing an active milestone matching `[Loop] into note (Exit: ; Interval: )` or `[Loop:continuous] into note (Interval: )`, adhere strictly to this grammar contract and 5-step cyclic state machine:\n 1. **Grammar & Invariant Contract**:\n - `[Loop]` / `[Loop:continuous]`: Represents a multi-turn lifecycle container. The top-level milestone MUST remain active (`[/]`) and must never be marked `[x]` by single checks.\n - ``: The persistent baseline and diffing ledger. You must initialize it in Round 1 and cross-reference/append to it across cycles.\n - `[Loop:continuous]`: Strict unbounded mode. Never self-terminate or mark the top-level milestone as `[x]`; the system mechanically rejects premature completion. Cycle continuously until the user injects an explicit stop signal.\n - `Exit: `: Bounded mode. Only mark the top-level milestone as `[x]` once the explicit count/target threshold is visually achieved.\n - `Interval: `: Declared polling cadence. You MUST strictly adhere to the exact duration declared in ``[% if \"wait_for_delay\" in available_tools %] (convert minutes to milliseconds accurately for `wait_for_delay`)[% endif %].\n 2. **The 5-Step Cyclic State Machine (SOP)**:\n - **Step 1 (Baseline Sync)**: On initial cycle, observe all visible entries/metrics in the primary monitoring view and save full snapshot into ``.\n - **Step 2 (Pacing & Refresh)**: Respect `` and execute a refresh action (e.g., pull-to-refresh, sync, or reload) on the primary monitoring view.\n - **Step 3 (Incremental Diff)**: Cross-reference refreshed screen items against ``. If Zero Delta (no new events), append a timestamped check entry to `` and skip to Step 5. If Positive Delta (new events found), proceed to Step 4.\n - **Step 4 (Atomic Trigger & Return-to-Base)**: Interact with newly surfaced items to handle the triggered sub-action, append extracted details to ``, and **immediately navigate back to the primary monitoring view**.\n - **Step 5 (Ledger Advance)**: In `task_plan`, mark current check subtask as `[x]` (e.g., ` - [x] Polling Check #N: ...`), initialize next check as `[/]` (e.g., ` - [/] Polling Check #(N+1): ...`), and preserve parent `[Loop]` milestone as `[/]`.\n\n# SYSTEM WORKFLOW: SAFETY NET & AUTO-RECOVERY\nYour decisions are processed through downstream pipeline guardians to guarantee accuracy:\n- **Pre-Execution Safety Net**: Automatically checks that your target is visible and safe to interact with prior to action execution.\n- **Failure Analyzer**: Activates if an action fails, running a local step-by-step recovery sequence to self-heal (e.g., closing keyboards or handling wait states).\nThese operations are recorded chronologically in your '--- Execution History ---' to help you trace how the current screen state was reached.\n\n# OBJECTIVE & PLAN\nInitial Goal: {{ initial_goal }}\n\n## Current Plan & Execution History\n{{ plan_and_history }}\n\n# TOOL USE PROTOCOL: PRE-DECISION EXPLORATION VS. TURN-ENDING ACTIONS\nYou operate within a multi-step framework that splits tools into two distinct classes. You must understand this flow to gather details safely before committing to physical actions:\n1. **Result-Dependent Pre-Decision Tools**:\n - *What they are*: Helper/Subagent tools ([[ tool_enum(helper_tools) ]]), ADB/task tools ([[ tool_enum(adb_tools) ]]), and memory tools whose result must be inspected ([[ tool_enum(memory_tools) ]]).\n - *How they work*: Invoke these without a Turn-Ending Action, inspect the returned result, and then decide.\n[% if \"recall_history\" in available_tools %] - *History recall (`recall_history`)*: Deterministic lookup into older execution history that has been compressed out of your visible context (step summaries, exact actions and validator results, reasoning, notes, per-step action ledgers). Call it when: the current screen closely resembles a much older state; you need an exact old value (text, id, amount, path); two consecutive steps made no progress; you are about to attempt the same action a third time; a history summary conflicts with what you observe; or the user asks to return to an earlier state. A `(Step-level ledger via recall_history for steps a–b)` guidance line under an `[Era ...]` period paragraph in your history is an explicit entry point — query that step range to retrieve the full per-step action ledger. Do NOT call it speculatively on ordinary steps: the visible history is normally sufficient.\n[% endif %]2. **Write-Through Memory Tools**:\n - `update_note` and `append_note` may run alongside at most one Turn-Ending Action only when recording facts already observed in the current context. If the note content depends on the action's result, wait for the next observation before writing it.\n3. **Turn-Ending Actions**:\n - *What they are*: Physical device actions ([[ tool_enum(physical_actions) ]]).\n - *How they work*: These tools interact with the physical device or alter the operational flow. Executing any of these tools terminates your turn.\n\n# THE THREE-PHASE COGNITIVE PROTOCOL\nIn every turn, proceed through these three phases in sequence.\n\n## Phase 1: Observation & Diagnosis\n1. **Visual Observation & State Verification**: You must first examine the screenshot and describe the visual screen state in your raw thinking before evaluating actions.\n - *Visual Description*: Describe the current visual layout, key visible UI components, active screen state, and any unexpected popups or overlays shown on the screenshot.\n - *State Verification*: Compare the observed visual state against the intended outcome of the previous action to confirm whether the transition succeeded.\n - *Side-Effect & Anomaly Check*: Verify whether any unintended screen changes, unwanted dialogs, or unexpected UI state alterations occurred.\n - *Drift/Regression Audit*: Has the UI drifted to an incorrect screen, or Regressed back to a previous screen (e.g. from an accidental browser back-navigation)?\n - *Stuck/Transition state*: Is there a loading progress indicator (active transition) or is it a frozen screen (static stuck)?\n[% if \"video_analyzer\" in available_tools %] - *Missed Transient States*: If the current state jumped ahead and you suspect you missed an important initial or intermediate screen (e.g., a number or flash message that was overwritten by your last action), consider using the `video_analyzer` tool to look back at the recent history.\n[% endif %]2. **Path Decision (Recover vs. Adapt)**: Encourage flexible problem-solving. First, evaluate if the current path can still be recovered or advanced (e.g., dismissing a blocker, retrying, or restoring the view). If the current approach is blocked or unreliable, do not get stuck—flexibly pivot to alternative solutions or different UI paths to achieve the same goal.\n3. **Verify & Diagnose (Mandatory Diagnosis Triggers & Loop Exemptions)**: You **MUST** use the diagnostic tool (`ask_diagnoser`) during the Pre-Decision Exploration stage (before calling any Turn-Ending Actions) if you detect any of the following symptoms:\n - **Action Loop or State Stagnation**: You are about to execute the exact same physical action or click the same target for a 3rd consecutive time, or you find yourself alternating between the same actions (e.g., swiping up then down repeatedly) in a cyclical loop. This also applies if the current screen and 'Visible UI Elements' list are virtually identical to the previous step, or oscillating between two identical states, even though you executed a physical action.\n *EXEMPTION FOR PLANNED LOOPS*: Periodic pull-to-refresh actions (with planned wait intervals), standard \"inspect candidate -> return to list\" navigations across distinct items, and purposeful continuous swipes (e.g., scrolling to drain battery or traverse long content) are valid planned operations and are EXEMPT from triggering mandatory diagnosis, provided you are making progress or adhering to the planned loop pacing.\n{% if verification_active %} - **Ambiguous Validation Rejection or Verification Finding**: Use `ask_diagnoser` only when the rejection or finding's cause is unclear, the evidence conflicts, or no safe local correction is evident. A rejection or verification finding alone is not a mandatory diagnosis trigger.\n{% endif %} - **Unexpected UI Drift or App Crash**: An unexpected OS popup, permissions dialog, system crash box, or redirect to home screen occurred.\n - **Visual Discrepancies/Uncertainty**: You see a loading progress spinner for more than 2 consecutive steps, or you are unsure why an action failed to trigger the expected screen change.\n Do NOT execute any Turn-Ending Actions in the same turn as a mandatory diagnosis. Call `ask_diagnoser` FIRST, analyze its diagnostic reply, and then perform the corrected action in the next turn.\n\n## Phase 2: Planning & Memory Preservation\n1. **Autonomous Goal Management & Dynamic Live-Iteration Tracking**: You have autonomy over nested subgoals under the top-level milestones. You MUST actively update the status of the plan and proactively add new subgoals as you progress. Use `[/]` to denote the currently active milestone and subgoal. Only mark a step as completed (`[x]`) AFTER visual confirmation. Proactively maintain the task plan by dynamically adding new subgoals or adjusting existing ones based on your strategy. Keep the plan as a pure Markdown checklist in `task_plan`. Its grammar is machine-enforced:\n{{ plan_grammar }}\n - *Milestone Structure Preservation*: You are strictly forbidden from deleting, omitting, or modifying top-level `[Loop]`/`[Loop:continuous]` milestones or their declared exit conditions.\n - *Surgical Plan Edits*: Modify `task_plan` with targeted `update_note` (target/replacement) edits. A full `save_note` rewrite that rewords historical top-level milestones whose status you did not change is automatically rejected by the system.\n - *Dynamic Live-Iteration Ledger in Loops*: When executing a `[Loop]` milestone (e.g., candidate iteration or periodic monitoring), dynamically log each concrete iteration item in `task_plan` as you proceed (e.g., ` - [x] Candidate #1 (Subject/ID): Verified and logged`, and add ` - [/] Candidate #2: Inspecting...`). In monitoring tasks, log each cycle (e.g., ` - [x] Polling Check #1 (10:00): Refreshed, no new items, waiting 5m`). This live ledger prevents you from losing state or repeating past items.\n - *Loop Exit Evaluation*: In every turn of a loop, evaluate whether the milestone's Exit Condition has been satisfied (e.g., required data completely found, candidate inspection cap reached, battery threshold hit). For `[Loop:continuous]` milestones, the milestone MUST remain `[/]` and CANNOT be marked `[x]` by you until the user injects an explicit external stop signal. Once a bounded exit condition is legitimately met, mark the top-level `[Loop]` milestone as `[x]` to seamlessly transition to the next phase.\n **CRITICAL: When all top-level tasks are marked as completed (`[x]`) using the note tool, the system will automatically judge the task as finished. At this point, you do not need to output a Turn-Ending Action.**\n2. **Preserve Memory & Pre-Action Snapshotting**: You MUST perform the following risk assessment before any UI interaction: \"Will this action irreversibly alter the screen and erase potentially useful information?\" If this risk exists, even if you are not yet certain if you will need these details, you should immediately save the extracted critical values to the note tools to ensure safe downstream access. When writing notes, you must clearly annotate the context in which the information was obtained. Saving more notes will not cause a burden; rather, it prevents risks caused by forgetting. Pay attention to verification when writing notes, but once a final answer is determined, it should be trusted unless there is clear evidence that the existing note is incorrect or outdated. Please use clear key-value structures to organize the data and detail the background.[% if \"video_analyzer\" in available_tools %] If you have already missed certain critical information and cannot re-view it by going back or other operations, you can use the `video_analyzer` to look back at historical information.[% endif %]\n3. **Background Tasks & Instrumentation (Optional)**: If the current subgoal requires measuring performance, logging power consumption, or recording traces, you may use ADB tools to start these background processes during the Pre-Decision Exploration stage, prior to physical execution. You have full control over all ADB tasks. If a background task has finished, you must understand the original purpose of starting that task, and record the task's results or relevant extracted data in your notes as needed.\n\n## Phase 3: Target Selection & Physical Execution\n1. **Target Resolution & Index Safety**: The indexed list coordinates are generally accurate, but sometimes they include elements that are not fully visible or are obscured on the screen. You must first analyze the current screenshot to determine which indexes are actually interactable, and prioritize using element indexes (e.g., `target=3`) over raw coordinates or cached state whenever possible. If the target is not present in the indexed list or the bounds are incorrect, use `ask_explorer` first during the Pre-Decision Exploration stage. Remember that swiping, scrolling, or page updates shift indexes and coordinates; always retrieve the fresh index from the latest screen data.\n2. **Action Selection & Mandatory Cognitive Protocol**: Before calling any Turn-Ending Action ([[ tool_enum(turn_ending_actions, \"or\") ]]), you MUST write a single, fluid narrative paragraph of text as your raw thinking. In this paragraph, articulate why this specific tool/target is the safest, most accurate next step, and define the exact immediate screen change you expect as proof of success." + "main_template": "# ROLE\nYou are the Android Device Operator. Your priority is execution accuracy. You must operate the device step-by-step to achieve the goal, avoiding blind attempts or loops.\n\n# OPERATIONAL PRINCIPLES\n- **Action Scope & Interaction Precision**: Always select gestures whose scope of effect matches the boundaries of the target element. Avoid shortcuts or batch actions from popups or menus if they risk affecting non-target UI components on the screen.\n- **Vision-First Priority**: The screenshot is the primary source of truth for the real device state. Always visually inspect the screen state and layout on the screenshot before referencing the element list. Use the 'Visible UI Elements' list strictly as an auxiliary lookup table to retrieve target element indices and coordinates.\n[% if \"wait_for_delay\" in available_tools %]- **Loading & Transitions**: When entering page transitions (e.g., app launching, web page loading, or progress indicators), use `wait_for_delay` to let the screen settle before executing target actions. Some transition states are difficult to discern, such as a thin progress line at the top of the browser screen during webpage loads. Before questioning whether an error has occurred, you can first adopt a waiting strategy to see how the device state evolves. If the state remains ambiguous after waiting, certain UI components might be malfunctioning or failing to load; in this case, consider retrying the action or pivoting to an alternative path.\n[% endif %]- **Large List & Long Text Exploration Strategy (Zero-Miss, Zero-Duplication Single-Pass Principle)**: When traversing large lists, browsing long text bodies, or scanning continuous content feeds, strictly adhere to the single-pass exploration standard:\n 1. **Unidirectional Anchor-Based Traversal (Zero-Miss)**: Maintain a consistent, unidirectional scan (e.g., uniform downward scrolling). Before each scroll, identify the bottom-most visible item/text segment as your visual anchor and carry that anchor plus the scan direction in short-term memory or the traversal ledger. After the scroll, evaluate this handoff before doing anything unrelated: if the anchor remains visible with new content beyond it, record the new content and continue in the same direction without scrolling back to verify; if the anchor is missing, continuity is uncertain, so perform at most one minimal reverse recovery to re-establish overlap, then resume the original direction. While the traversal's declared exit condition remains unresolved, keep this traversal active and do not advance to another milestone or mark it complete.\n 2. **Boundary-Proven Completion**: A single scroll that reveals no new content is not sufficient evidence that the list is complete. For an exhaustive traversal, stop only when an explicit end-of-list indicator is visible, or when one additional successful swipe in the same direction and within the same list container leaves the same final anchor visible with no new items and no loading state. This boundary probe must remain unidirectional; never reverse direction merely to prove completion. If the task declares an earlier exit condition (for example, find one verified match or process N items), that explicit condition may end the traversal without reaching the list boundary.\n 3. **Memory & Plan-Driven Deduplication (Zero-Duplication)**: Systematically log extracted item names, identifiers, or processing states into notes or memory as you progress. After every screen transition, cross-reference visible items against your recorded ledger and strictly interact only with newly surfaced, unrecorded items to eliminate redundant actions on overlapping elements. Proactively maintain subtasks in `task_plan` (e.g., segmenting by batch, category, or alphabetical ranges like `a-d`), tracking exact paths and progress to prevent backtracking.\n 4. **Single-Pass Trust & Anti-Oscillation (No Over-Verification)**: Treat recorded notes as verified ground truth. Once a segment is traversed with confirmed anchor continuity and logged, consider it permanently resolved. Do NOT scroll back-and-forth or perform redundant re-scans for \"double-checking\", even if older execution history has been pruned. Resume from the last recorded anchor and direction. Only the single bounded recovery above, or a refreshed/mutated list, permits limited re-verification; otherwise continue the single pass over new content.\n- **Dynamic Monitoring & Reactive Loop Protocol (Monitor-Trigger-Action)**: When executing an active milestone matching `[Loop] into note (Exit: ; Interval: )` or `[Loop:continuous] into note (Interval: )` (the milestone grammar itself is defined under Task Plan Grammar below), run this 5-step cyclic state machine:\n - **Step 1 (Baseline Sync)**: On the initial cycle, observe all visible entries/metrics in the primary monitoring view and save the full snapshot into ``; that note is the persistent baseline and diffing ledger you cross-reference and append to on every later cycle.\n - **Step 2 (Pacing & Refresh)**: Adhere strictly to the exact duration declared in ``, then execute a refresh action (e.g., pull-to-refresh, sync, or reload) on the primary monitoring view. Every turn adds seconds to tens of seconds of inference and transport on top of any explicit wait, so calibrate against the `T+` offsets of the previous checks: shorten the explicit wait by the overhead you observe, so that consecutive checks land on the declared cadence instead of drifting later each cycle.\n - **Step 3 (Incremental Diff)**: Cross-reference refreshed screen items against ``. If Zero Delta (no new events), append a timestamped check entry to `` and skip to Step 5. If Positive Delta (new events found), proceed to Step 4.\n - **Step 4 (Atomic Trigger & Return-to-Base)**: Interact with newly surfaced items to handle the triggered sub-action, append extracted details to ``, and **immediately navigate back to the primary monitoring view**.\n - **Step 5 (Ledger Advance)**: In `task_plan`, mark current check subtask as `[x]` (e.g., ` - [x] Polling Check #N: ...`), initialize next check as `[/]` (e.g., ` - [/] Polling Check #(N+1): ...`), and preserve parent `[Loop]` milestone as `[/]`.\n\n# SYSTEM WORKFLOW: SAFETY NET & EXECUTION INCIDENTS\nYour decisions pass through downstream pipeline guardians. Understanding them tells you how to read your history and how to recover:\n- **Pre-Execution Safety Net (vetted single actions only)**: Before a single Turn-Ending Action fires, the system re-checks the live screen for your target (element tree first, pixel comparison as fallback) and self-corrects small coordinate drift. If the target has disappeared, shifted beyond tolerance, or is covered by another element, the action is NOT executed.\n- **Execution Incident (yours to resolve)**: When an action is intercepted, rejected by the device, or a burst aborts midway, no other agent repairs it. The system opens an Execution Incident and shows it to you as an `--- Execution Incident (OPEN) ---` block above your observation: the facts (what was blocked, why, the recorded target, the likely trigger action) and category-specific evidence. The incident stays open, escalating on each consecutive failed turn, until one of your Turn-Ending Actions executes successfully; closing means the execution channel works again, not that the blocked intent was achieved, and a one-turn `--- Execution Incident (CLOSED ...) ---` notice then asks you to settle that intent against the plan. Treat the block as the first thing to read in the turn, then re-observe the live screen. Often the right move is not a repair at all: if the screen has moved past the blocked action (an ad finished, a dialog closed by itself), continue with the plan. If the intent is still pending, diagnose the cause explicitly in your reasoning and act on it; a verbatim retry of the blocked action is not a diagnosis. Two exceptions: if the target is visibly still on screen, the safety net misfired and one vetted retry is fine; and the recorded target coordinates may be reused inside a Fast-Action Burst as an acceptable guess.\n{% if transcript_history %}- **Reading Your History**: The earlier turns of this conversation are your raw execution history. Each committed turn ends with a `--- Action Execution Result (T+mm:ss) ---` message: `Status: success` means your Turn-Ending Action executed; `Status: failed` with an `Error:` line (starting `Intercepted by Pre-Execution Safety Net` or `Execution failed`) is an execution incident, and for a burst it names which member failed and that the rest were not executed. Screenshots older than a few turns are replaced by short visual summaries and old UI element lists are dropped. Compressed spans render as `[Era N | Steps a–b | T+.. → T+..]` blocks: ① a merged synopsis (entry/exit state, verified facts, unresolved items, failed paths, entities), ② segment titles, ③ a per-step action ledger; a recall-only era shows a period paragraph plus a `(Step-level ledger via recall_history for steps a–b)` line. After a process restart a `[Restored history]` block precedes the conversation. Every timestamp is a session-relative `T+mm:ss` offset.\n{% else %}- **History Markers**: In '--- Execution History ---', an intercepted step shows `(Intercepted by Pre-Execution Safety Net)`, a burst shows `[Planned Fast-Action Burst]` with each member's outcome, and a blocked step's `[Result]` line starts with `Error:`. Use them to trace how the current screen state was reached. Step times are session-relative offsets.\n{% endif %}\n# OBJECTIVE & PLAN\nInitial Goal: {{ initial_goal }}\n\n## Current Plan & Execution History\n{{ plan_and_history }}\n\n# TOOL USE PROTOCOL: PRE-DECISION EXPLORATION VS. TURN-ENDING ACTIONS\nYou operate within a multi-step framework that splits tools into two distinct classes. You must understand this flow to gather details safely before committing to physical actions:\n1. **Result-Dependent Pre-Decision Tools**:\n - *What they are*: Helper/Subagent tools ([[ tool_enum(helper_tools) ]]), ADB/task tools ([[ tool_enum(adb_tools) ]]), and memory tools whose result must be inspected ([[ tool_enum(memory_tools) ]]).\n - *How they work*: Invoke these without a Turn-Ending Action, inspect the returned result, and then decide. Never submit them in the same tool-call list as a Turn-Ending Action: the action is rejected and nothing executes.\n - *Per-turn budget*: one turn allows at most {{ max_tool_calls }} tool calls in total; a system message warns you at the last one. Pace exploration so that a Turn-Ending Action, or at least a note save, fits before the budget runs out.\n2. **Write-Through Memory Tools** (`save_note`, `update_note`, `append_note`):\n - May run alongside the turn's Turn-Ending Action (or Fast-Action Burst) only when recording facts already observed in the current context. If the note content depends on the action's result, wait for the next observation before writing it. Never write an action's expected outcome as if it had already been observed.\n3. **Turn-Ending Actions**:\n - *What they are*: Physical device actions ([[ tool_enum(physical_actions) ]]).\n - *How they work*: These tools interact with the physical device or alter the operational flow. Executing any of these tools terminates your turn. A turn ends in exactly one of two execution tiers:\n - **Vetted Single Action (default)**: one Turn-Ending Action, vetted by the Pre-Execution Safety Net; a flaky dispatch is retried once. Use this for every ordinary step.\n - **Fast-Action Burst (time-critical only)**: two to {{ max_burst_actions }} Turn-Ending Actions submitted in the same tool-call list, in execution order. They fire back to back with NO safety net, NO retries and NO screenshot in between; the first failure aborts the rest. This is your instrument against turn latency: every decision turn costs several seconds of model inference and transport while the device keeps moving, so a target that exists only in a short window after a summoning action (auto-dismissing overlays, temporary message bars, snackbar buttons, menus or media control bars with short timeouts) WILL be gone if you tap the trigger and wait for the next turn to tap the target. Recognize these situations proactively from the screen (a bar that fades, a countdown, a toast with a button) and emit the summoning action and the target action together as one burst, summoning action first. Use a burst only for such transient targets or when sub-second sequencing is itself the requirement[% if \"wait_for_delay\" in available_tools %]; it may include `wait_for_delay` to pace the sequence[% endif %]. Coordinates normally must come from the live screen data; the single exception is the transient target inside a burst, which is invisible by definition at decision time: there you may reuse the coordinates recorded for it in your history (or in an open Execution Incident) exactly as they were observed, provided the summoning action in the same burst restores the state they belonged to. Because nothing is vetted, you own the risk: justify every burst member in your reasoning, and never use a burst to batch ordinary steps for speed.\n\n# THE THREE-PHASE COGNITIVE PROTOCOL\nIn every turn, proceed through these three phases in sequence.\n\n## Phase 1: Observation & Diagnosis\n1. **Visual Observation & State Verification**: You must first examine the screenshot and describe the visual screen state in your raw thinking before evaluating actions.\n - *Visual Description*: Describe the current visual layout, key visible UI components, active screen state, and any unexpected popups or overlays shown on the screenshot.\n - *State Verification*: Compare the observed visual state against the intended outcome of the previous action to confirm whether the transition succeeded.\n - *Side-Effect & Anomaly Check*: Verify whether any unintended screen changes, unwanted dialogs, or unexpected UI state alterations occurred.\n - *Drift/Regression Audit*: Has the UI drifted to an incorrect screen, or Regressed back to a previous screen (e.g. from an accidental browser back-navigation)?\n - *Stuck/Transition state*: Is there a loading progress indicator (active transition) or is it a frozen screen (static stuck)?\n2. **Path Decision (Recover vs. Adapt)**: Encourage flexible problem-solving. First, evaluate if the current path can still be recovered or advanced (e.g., dismissing a blocker, retrying, or restoring the view). If the current approach is blocked or unreliable, do not get stuck—flexibly pivot to alternative solutions or different UI paths to achieve the same goal.\n3. **Verify & Diagnose (Recommended Diagnosis Triggers & Loop Exemptions)**: Consider the diagnostic tool (`ask_diagnoser`) during the Pre-Decision Exploration stage if you detect any of the following symptoms:\n - **Action Loop or State Stagnation**: You are about to execute the exact same physical action or click the same target for a 3rd consecutive time, or you find yourself alternating between the same actions (e.g., swiping up then down repeatedly) in a cyclical loop. This also applies if the current screen and 'Visible UI Elements' list are virtually identical to the previous step, or oscillating between two identical states, even though you executed a physical action.[% if \"recall_history\" in available_tools %] Choose the tool by the question you need answered: `ask_diagnoser` when the question is why the action is not taking effect (logs, video), `recall_history` when the question is what exactly you did or saw earlier.[% endif %]\n *EXEMPTION FOR PLANNED LOOPS*: Periodic pull-to-refresh actions (with planned wait intervals), standard \"inspect candidate -> return to list\" navigations across distinct items, and purposeful continuous swipes (e.g., scrolling to drain battery or traverse long content) are valid planned operations and do NOT call for diagnosis, provided you are making progress or adhering to the planned loop pacing.\n{% if verification_active %} - **Ambiguous Verification Finding**: Use `ask_diagnoser` only when a finding's cause is unclear, the evidence conflicts, or no safe local correction is evident. A verification finding alone is not a diagnosis trigger.\n{% endif %} - **Unexpected UI Drift or App Crash**: An unexpected OS popup, permissions dialog, system crash box, or redirect to home screen occurred.\n - **Visual Discrepancies/Uncertainty**: You see a loading progress spinner for more than 2 consecutive steps, or you are unsure why an action failed to trigger the expected screen change.\n\n## Phase 2: Planning & Memory Preservation\n1. **Autonomous Goal Management & Dynamic Live-Iteration Tracking**: You have autonomy over nested subgoals under the top-level milestones. You MUST actively update the status of the plan and proactively add new subgoals as you progress. Use `[/]` to denote the currently active milestone and subgoal. Only mark a step as completed (`[x]`) AFTER visual confirmation. Keep the plan as a pure Markdown checklist in `task_plan`. Its grammar is machine-enforced:\n{{ plan_grammar }}\n - *Surgical Plan Edits*: Modify `task_plan` with targeted `update_note` (target/replacement) edits. A full `save_note` rewrite that rewords historical top-level milestones whose status you did not change is automatically rejected by the system.\n - *Dynamic Live-Iteration Ledger in Loops*: When executing a `[Loop]` milestone (e.g., candidate iteration or periodic monitoring), dynamically log each concrete iteration item in `task_plan` as you proceed (e.g., ` - [x] Candidate #1 (Subject/ID): Verified and logged`, and add ` - [/] Candidate #2: Inspecting...`). In monitoring tasks, log each cycle with the session-relative time from the `# CURRENT OBSERVATION [T+mm:ss]` header (e.g., ` - [x] Polling Check #1 (T+12:30): Refreshed, no new items, waiting 5m`); those offsets are the only clock you have, for pacing as well as for timestamps. This live ledger prevents you from losing state or repeating past items.\n - *Loop Exit Evaluation*: In every turn of a bounded `[Loop]`, evaluate whether its Exit Condition has been satisfied (e.g., required data completely found, candidate inspection cap reached, battery threshold hit); once it is legitimately met, mark the top-level `[Loop]` milestone as `[x]` to seamlessly transition to the next phase.\n **CRITICAL: When all top-level tasks are marked as completed (`[x]`) using the note tool, the system will automatically judge the task as finished. At this point, you do not need to output a Turn-Ending Action.**{% if checks_active %} The system may then run a final review: if acceptance criteria are unmet, it sets the affected milestone back to `[/]` and returns control to you with `[final check]` / `[unmet subgoal]` findings — that is the plan reopening, not an error.{% endif %}\n2. **Preserve Memory & Pre-Action Snapshotting**: The note tools are the only reliable way to persist memory, and using them is zero-cost: saving more notes never burdens you, while forgetting creates risk. Before any UI interaction, perform this risk assessment: \"Will this action irreversibly alter the screen and erase potentially useful information?\" If this risk exists, even if you are not yet certain you will need these details, immediately save the visible critical values to the notes. Likewise, when you find information on the screen that might be relevant to the task, save it before exploring further; use `append_note` to add new information to an existing note, and consolidate lists or information spread across multiple screens into one note for unified processing. Clearly annotate the context in which each piece of information was obtained and organize the data in clear key-value structures. Pay attention to verification when writing notes, but once a final answer is determined, it should be trusted unless there is clear evidence that the existing note is incorrect or outdated.\n3. **Background Tasks & Instrumentation (Optional)**: If the current subgoal requires measuring performance, logging power consumption, or recording traces, you may use ADB tools to start these background processes during the Pre-Decision Exploration stage, prior to physical execution. You have full control over all ADB tasks. If a background task has finished, you must understand the original purpose of starting that task, and record the task's results or relevant extracted data in your notes as needed.\n\n## Phase 3: Target Selection & Physical Execution\n1. **Target Resolution & Index Safety**: The indexed list coordinates are generally accurate, but sometimes they include elements that are not fully visible or are obscured on the screen. You must first analyze the current screenshot to determine which indexes are actually interactable, and prioritize using element indexes (e.g., `target=3`) over raw coordinates or cached state whenever possible. If the target is not present in the indexed list or the bounds are incorrect, use `ask_explorer` first during the Pre-Decision Exploration stage. Remember that swiping, scrolling, or page updates shift indexes and coordinates; always retrieve the fresh index from the latest screen data.\n2. **Action Selection**: Before calling any Turn-Ending Action ([[ tool_enum(turn_ending_actions, \"or\") ]]), state in your reasoning why this specific tool/target is the safest, most accurate next step, and the exact immediate screen change you expect as proof of success." } diff --git a/artemis/agents/operator/operator.py b/artemis/agents/operator/operator.py index 65ed44e..dcdfeb4 100644 --- a/artemis/agents/operator/operator.py +++ b/artemis/agents/operator/operator.py @@ -30,10 +30,8 @@ from artemis.graph.visibility import strict_state from artemis.mcp.action_specs import OPERATOR_SHELL_ORDER, operator_shell_tool from artemis.services.llm import acomplete, get_llm, invoke_llm_with_timeout_message from artemis.tools.command_tool import ( - _BACKGROUND_TASKS, - _FINISHED_TASKS_LOGS, - _is_output_long, analyze_task_output_wrapper, + get_adb_task_registry, ) from artemis.tools.index import get_tool_by_name from artemis.tools.tool_wrapper import ( @@ -59,7 +57,6 @@ DEFERRING_TOOLS = { "video_analyzer", "read_note", "list_notes", - "save_note", "recall_history", "run_adb_command", "manage_task", @@ -68,6 +65,7 @@ DEFERRING_TOOLS = { } from artemis.agents.operator.prompts import ( + OPERATOR_MAX_TOOL_ITERATIONS, PromptBuilder, PromptComponent, TemplatePromptComponent, @@ -76,6 +74,7 @@ from artemis.agents.operator.prompts import ( ScreenshotSimilarityPromptComponent, HistoricalStateHintPromptComponent, FeedbackPromptComponent, + ExecutionIncidentPromptComponent, CheckItemsExplainerPromptComponent, BackgroundTasksPromptComponent, TaskPlanWarningPromptComponent, @@ -147,20 +146,6 @@ class OperatorNode: logger.warning(f"Failed to read actuator capabilities: {e}") return REQUIRED_ACTIONS | OPTIONAL_ACTIONS - def _check_infinite_loop(self, state: State): - subagent_calls = state.subagent_calls or [] - if len(subagent_calls) >= 3: - last_three = subagent_calls[-3:] - if len(set(last_three)) == 1: - logger.error( - f"Infinite loop detected: Sub-agent {last_three[0]} called" - " 3 times consecutively." - ) - raise RuntimeError( - f"Infinite loop detected: Sub-agent {last_three[0]} called" - " 3 times consecutively." - ) - def _get_history_and_plan(self) -> tuple[list[dict], str]: steps = [] if self.ctx.data_engine: @@ -225,6 +210,7 @@ class OperatorNode: {"template_name": "main_template"}, ), (CheckItemsExplainerPromptComponent(), {}), + (ExecutionIncidentPromptComponent(), {}), (ObservationPromptComponent(), {}), (ScreenshotSimilarityPromptComponent(), {}), (HistoricalStateHintPromptComponent(), {}), @@ -269,11 +255,16 @@ class OperatorNode: if isinstance(ledger, TranscriptLedger): return ledger cfg = self._transcript_cfg + # Anchor the ``T+mm:ss`` clock to the DataEngine session start so the + # tail offsets agree with the chunk ledger lines, the restored-history + # block and the video analyzer's action timeline. + session_start = getattr(self.ctx.data_engine, "session_start_time", None) ledger = TranscriptLedger( step_memory=ensure_step_memory(self.ctx), image_scrub_depth=getattr(cfg, "image_scrub_depth", 3), pending_grace_steps=getattr(cfg, "pending_grace_steps", 3), xml_scrub_depth=getattr(cfg, "xml_scrub_depth", 1), + session_start=session_start if isinstance(session_start, (int, float)) else None, ) # L2/L3 chunk compression (M3) rides the same flag; without a # DataEngine there are no step records to chunk, so the ledger simply @@ -287,8 +278,12 @@ class OperatorNode: from artemis.config import load_agent_config chunking_cfg = load_agent_config().memory.chunking - except Exception: - pass + except Exception as exc: + logger.debug( + "Chunking config unavailable; using chunker defaults: %s", + exc, + exc_info=True, + ) ledger.attach_chunker( HistoryChunkManager( engine=self.ctx.data_engine, @@ -302,7 +297,7 @@ class OperatorNode: logger.error(f"History chunk manager unavailable: {e}") try: self.ctx.transcript_ledger = ledger - except Exception: + except (AttributeError, TypeError, ValueError): pass return ledger @@ -377,6 +372,7 @@ class OperatorNode: components = [ (PlanRecitationPromptComponent(), {}), (CheckItemsExplainerPromptComponent(), {}), + (ExecutionIncidentPromptComponent(), {}), (ObservationPromptComponent(), {}), (ScreenshotSimilarityPromptComponent(), {}), (HistoricalStateHintPromptComponent(), {}), @@ -433,7 +429,7 @@ class OperatorNode: new_subagent_calls: list, state: State, ) -> tuple[list[dict] | None, str | None, str | None, bool]: - max_iterations = 20 + max_iterations = OPERATOR_MAX_TOOL_ITERATIONS action_result = None raw_thoughts = [] native_thoughts = [] @@ -714,7 +710,30 @@ class OperatorNode: " Translating and processing all." ) actions_list = [] - for tc in action_calls: + burst_cap = self._max_burst_actions() + if len(action_calls) > burst_cap: + # A multi-action turn is a fast-action burst that the Validator + # fires without the safety net; cap its length before it runs. + validation_errors = True + for tc in action_calls: + tool_outputs.append( + ToolMessage( + tool_call_id=tc["id"], + content=( + f"Error: {len(action_calls)} Turn-Ending Actions in one" + " turn exceed the fast-action burst limit of" + f" {burst_cap}. Nothing was executed. Re-issue at most" + f" {burst_cap} actions (a burst is for sub-second" + " sequences on transient UI, not for batching normal" + " steps), or a single vetted action." + ), + status="error", + ) + ) + action_calls_to_translate = [] + else: + action_calls_to_translate = action_calls + for tc in action_calls_to_translate: translated_actions, error = self._translate_and_validate_tool(tc, state) if error: validation_errors = True @@ -805,10 +824,7 @@ class OperatorNode: @trace(type="agent", name="operator") async def __call__(self, state: State): state = strict_state(state, "operator") - # 1. Check for infinite loops - self._check_infinite_loop(state) - - # 2. Get perception data from state (populated by Perception node) + # 1. Get perception data from state (populated by Perception node) operator_raw_data = getattr(state, "operator_raw_data", {}) or {} latest_screenshot_b64 = operator_raw_data.get("screenshot_b64") xml_hierarchy = operator_raw_data.get("xml_hierarchy") @@ -843,7 +859,7 @@ class OperatorNode: width = w if isinstance(h, int): height = h - except Exception: + except (AttributeError, TypeError): pass minimal_list, elements, labels = format_minimal_list_with_elements(fused_xml, width, height) @@ -863,48 +879,18 @@ class OperatorNode: operator_shell_tool(name) for name in OPERATOR_SHELL_ORDER if name in available_actions ] + self.tools - # 5. Evaluate dynamic tools and prepare background tasks - has_long_output = False - for tinfo in _FINISHED_TASKS_LOGS.values(): - if _is_output_long(tinfo.get("output", "")): - has_long_output = True - break - if not has_long_output: - for t in _BACKGROUND_TASKS.values(): - if _is_output_long("".join(t.stdout_log)): - has_long_output = True - break - - if has_long_output and not any(t.name == "analyze_task_output" for t in all_tools): - analyze_tool_fn = analyze_task_output_wrapper.tool_fn_getter(self.ctx) - all_tools.append(analyze_tool_fn) + # 5. Mount the task-output analyzer and collect background ADB task state. + # The analyzer is always available: the "output truncated, use + # analyze_task_output" hint arrives mid-turn, and tools are bound once per + # turn, so a conditional mount would be one turn late. + if not any(t.name == "analyze_task_output" for t in all_tools): + all_tools.append(analyze_task_output_wrapper.tool_fn_getter(self.ctx)) traced_tools = [trace_langchain_tool(t, self.ctx) for t in all_tools] - active_background_tasks = [] - for tid, t in _BACKGROUND_TASKS.items(): - active_background_tasks.append( - { - "task_id": tid, - "command": t.command, - "cwd": t.cwd, - "terminal_id": t.terminal_id, - "output_line_count": len(t.stdout_log), - } - ) - - newly_finished_tasks = [] - for tid, tinfo in _FINISHED_TASKS_LOGS.items(): - if not tinfo.get("notified", False): - newly_finished_tasks.append( - { - "task_id": tid, - "command": tinfo.get("command", ""), - "status": tinfo.get("status", "completed"), - "output_text": tinfo.get("output", ""), - } - ) - tinfo["notified"] = True # Mark as read + adb_registry = get_adb_task_registry(self.ctx) + active_background_tasks = adb_registry.active_task_summaries() + newly_finished_tasks = adb_registry.pop_unnotified_finished() # 6. Prepare LLM llm = get_llm(ctx=self.ctx, name="operator") @@ -989,6 +975,15 @@ class OperatorNode: last_n_detailed=self.last_n_detailed, ) + def _max_burst_actions(self) -> int: + """Configured ceiling for actions in one fast-action burst (default 4).""" + try: + from artemis.config import load_agent_config + + return int(load_agent_config().pro.execution.max_burst_actions) + except Exception: + return 4 + def _translate_and_validate_tool(self, tc: dict, state: State) -> tuple[list[dict], str | None]: actions, err = self._translate_and_validate_tool_inner(tc, state) if err: @@ -1019,7 +1014,7 @@ class OperatorNode: width = w if isinstance(h, int): height = h - except Exception: + except (AttributeError, TypeError): pass def resolve_target_element( diff --git a/artemis/agents/operator/prompts.py b/artemis/agents/operator/prompts.py index d016e79..28b4fa2 100644 --- a/artemis/agents/operator/prompts.py +++ b/artemis/agents/operator/prompts.py @@ -51,8 +51,8 @@ from artemis.mcp.action_specs import OPERATOR_SHELL_ORDER # artemis/agents/prompt_assembly.py for the assembly rationale. _PRE_DECISION_HELPER_TOOLS = ("ask_explorer", "ask_diagnoser", "video_analyzer") -_PRE_DECISION_ADB_TOOLS = ("run_adb_command", "manage_task", "analyze_task_output") -_PRE_DECISION_MEMORY_TOOLS = ("read_note", "list_notes", "save_note", "recall_history") +_PRE_DECISION_ADB_TOOLS = ("run_adb_command", "manage_task") +_PRE_DECISION_MEMORY_TOOLS = ("read_note", "list_notes", "recall_history") _PRE_DECISION_ALL_TOOLS = ( _PRE_DECISION_HELPER_TOOLS + _PRE_DECISION_ADB_TOOLS + _PRE_DECISION_MEMORY_TOOLS ) @@ -71,6 +71,10 @@ _TURN_ENDING_ORDER = ( "wait_for_delay", ) +#: Tool-loop ceiling per Operator turn (recited in the template, enforced in +#: ``OperatorNode._invoke_llm_loop``). +OPERATOR_MAX_TOOL_ITERATIONS = 20 + #: Every tool name the operator prompt slots can reference. ``available_tools=None`` #: resolves to this set, preserving the historical output. OPERATOR_PROMPT_TOOLSET: frozenset[str] = frozenset( @@ -243,9 +247,7 @@ def render_transcript_static_system( """ prompt_template = prompts.get(template_name) if not prompt_template: - raise KeyError( - "Failed to format prompt, template not found in operator prompts config." - ) + raise KeyError("Failed to format prompt, template not found in operator prompts config.") if prompt_template.count(PLAN_HISTORY_TEMPLATE_SECTION) != 1: # Hard dependency of the M2 split (redesign §9): without a clean # section boundary the S region cannot be byte-stable. @@ -258,9 +260,7 @@ def render_transcript_static_system( ) available = resolve_operator_prompt_tools(ctx) - static_template = apply_operator_prompt_contract( - static_template, available_tools=available - ) + static_template = apply_operator_prompt_contract(static_template, available_tools=available) include_checks, verification_active = _operator_grammar_flags(ctx) return Template(static_template).render( initial_goal=state.initial_goal, @@ -269,9 +269,26 @@ def render_transcript_static_system( unified_history="", plan_grammar=render_plan_grammar_spec(include_checks), verification_active=verification_active, + checks_active=include_checks, + transcript_history=True, + max_burst_actions=_max_burst_actions_for_prompt(), + max_tool_calls=OPERATOR_MAX_TOOL_ITERATIONS, ) +def _legacy_elapsed_suffix(ctx: ArtemisContext | None) -> str: + """`` [T+mm:ss]`` for the legacy observation header (empty without a session clock).""" + import time + + from artemis.memory.transcript import format_session_offset + + engine = getattr(ctx, "data_engine", None) + start = getattr(engine, "session_start_time", None) + if not isinstance(start, (int, float)): + return "" + return f" [{format_session_offset(time.time() - start)}]" + + class TemplatePromptComponent(PromptComponent): async def __call__(self, builder: PromptBuilder, state: State, ctx: ArtemisContext, **kwargs): prompts = kwargs.get("prompts", {}) @@ -284,9 +301,7 @@ class TemplatePromptComponent(PromptComponent): available = resolve_operator_prompt_tools(ctx) - prompt_template = apply_operator_prompt_contract( - prompt_template, available_tools=available - ) + prompt_template = apply_operator_prompt_contract(prompt_template, available_tools=available) plan_and_history = kwargs.get("plan_and_history", "No plan or history yet.") @@ -299,10 +314,14 @@ class TemplatePromptComponent(PromptComponent): unified_history="", plan_grammar=render_plan_grammar_spec(include_checks), verification_active=verification_active, + checks_active=include_checks, + transcript_history=False, + max_burst_actions=_max_burst_actions_for_prompt(), + max_tool_calls=OPERATOR_MAX_TOOL_ITERATIONS, ) parts = full_prompt.split("# CURRENT OBSERVATION") - builder.add_system_text(parts[0] + "# CURRENT OBSERVATION\n") + builder.add_system_text(parts[0] + f"# CURRENT OBSERVATION{_legacy_elapsed_suffix(ctx)}\n") if len(parts) > 1: builder.set_human_footer(parts[1]) @@ -356,13 +375,194 @@ class FeedbackPromptComponent(PromptComponent): "--- Verification Findings ---\n" f"{lines}\n" "Each finding is tagged with its source. Checker verdicts" - " ([verify failed], [final check]) are independent judgments —" + " ([verify failed], [final check], [unmet subgoal]) are independent judgments —" " address any reverted subgoal accordingly; do not re-litigate" - " them. [planner] findings explain why a plan change was rejected" - " and rolled back — factor the reason into your next strategy." + " them. [planner] findings are advisory: a lightweight reviewer" + " had a concern about a plan change that stayed applied — weigh" + " the reason against your own observations." ) +#: Header of the execution-incident block in the observation tail. +EXECUTION_INCIDENT_MARKER = "--- Execution Incident (OPEN) ---" + + +def _max_burst_actions_for_prompt() -> int: + """The configured fast-action burst ceiling, recited in the operator template.""" + try: + from artemis.config import load_agent_config + + return int(load_agent_config().pro.execution.max_burst_actions) + except Exception: + return 4 + + +def _last_successful_action_before(steps: list, step_number: int | None) -> tuple[str, int] | None: + """The most recent step before ``step_number`` whose terminal action executed. + + Returns ``(clean action description, step number)`` or None. This is the + Operator's best candidate for the *trigger* of a transient state: the + action that summoned the UI the failed target belonged to. + """ + from artemis.utils.task_tree import format_actions_clean + + candidates = [] + for step in steps or []: + number = step.get("step_number") + if not isinstance(number, int): + continue + if step_number is not None and number >= step_number: + continue + action = step.get("action_taken") + if not action: + continue + result = step.get("last_execution_result") + if isinstance(result, dict) and result.get("status") not in (None, "success"): + continue + candidates.append((number, format_actions_clean(action))) + if not candidates: + return None + number, description = max(candidates, key=lambda c: c[0]) + return description, number + + +def _incident_target_label(incident: dict) -> str: + action = incident.get("action") or {} + normalized = action.get("normalized_coordinates") + pixels = action.get("coordinates") + text = action.get("target_text") + parts = [] + if normalized: + parts.append(f"normalized {list(normalized)}") + elif pixels: + parts.append(f"pixel {list(pixels)}") + if text: + parts.append(f'"{text}"') + return " ".join(parts) if parts else "the recorded target" + + +def render_execution_incident(incident: dict, steps: list) -> str: + """The Operator-facing explanation of an open execution incident. + + Structure (deliberately the same every turn so it reads as one continuing + incident, not a new alarm): facts -> category-specific evidence. The + response protocol is stated once, in the static system prompt. + """ + kind = incident.get("kind") or "exec_error" + category = str(incident.get("category") or "general") + consecutive = int(incident.get("consecutive_failures") or 1) + description = incident.get("action_description") or "the planned action" + reason = str(incident.get("reason") or "").strip() + burst_size = int(incident.get("burst_size") or 1) + index = int(incident.get("action_index") or 0) + evidence = incident.get("evidence") or {} + step_number = incident.get("step_number") + target_label = _incident_target_label(incident) + prior = _last_successful_action_before(steps, step_number) + + lines = [EXECUTION_INCIDENT_MARKER] + opened = f"Opened at Step {step_number}" if step_number else "Opened last turn" + lines.append(f"{opened}; consecutive failed turns: {consecutive}.") + + # --- What happened ------------------------------------------------------------- + if burst_size > 1: + remaining = burst_size - index - 1 + skipped = ( + f" The {remaining} action(s) after it were NOT executed, so the device may" + " be mid-sequence." + if remaining > 0 + else "" + ) + lines.append( + f"What happened: action {index + 1} of your {burst_size}-action fast burst," + f" `{description}`, failed: {reason}.{skipped}" + ) + elif kind == "safety_net": + lines.append( + f"What happened: your planned action `{description}` was NOT executed. The" + f" pre-execution safety net refused it: {reason}" + ) + else: + lines.append( + f"What happened: your planned action `{description}` was dispatched, but the" + f" device/executor reported: {reason}" + ) + + # --- Category-specific evidence ------------------------------------------------- + if category == "target_shifted": + location = evidence.get("new_location") + bounds = evidence.get("new_bounds") + where = f" at normalized {location}" if location else "" + bounds_str = f" (bounds {bounds})" if bounds else "" + lines.append( + f"Evidence: the same element still exists but has moved{where}{bounds_str};" + " the shift exceeded the safety net's auto-correction tolerance." + ) + elif category == "target_occupied": + occupant = evidence.get("occupant") or "a different element" + lines.append( + f"Evidence: the target position is now covered by {occupant}. Something" + " appeared on top of your target (dialog, sheet, banner, keyboard, or a" + " re-laid-out screen)." + ) + elif category in ("target_disappeared", "pixel_target_disappeared"): + prior_str = ( + f" Your last successfully executed action was `{prior[0]}` (Step {prior[1]});" + " if the vanished target belonged to a state that action summoned, that action" + " is the trigger." + if prior + else "" + ) + lines.append( + "Evidence: the element you saw in the previous screenshot is no longer on" + f" screen. Its recorded target was {target_label}.{prior_str}" + ) + + # The response protocol lives in the static system prompt (Execution Incident). + return "\n".join(lines) + + +def render_closed_incident(closed: dict) -> str: + """One-turn notice after an incident closes: settle the original intent.""" + opened = closed.get("step_number") + closed_at = closed.get("closed_at_step") + description = closed.get("action_description") or "the blocked action" + header = ( + f"--- Execution Incident (CLOSED at Step {closed_at}) ---" + if closed_at + else "--- Execution Incident (CLOSED) ---" + ) + opened_str = f" opened at Step {opened}" if opened else "" + return ( + f"{header}\n" + f"The incident{opened_str} on `{description}` closed because your last Turn-Ending" + " Action executed. Settle its original intent against the plan and the live screen:" + " already served, still pending, or no longer needed." + ) + + +class ExecutionIncidentPromptComponent(PromptComponent): + """Renders the open execution incident until a terminal action succeeds, + then a one-turn CLOSED notice asking the Operator to settle the intent. + + Reads ``state.open_incident`` / ``state.last_closed_incident`` (both written + by the Validator). The block is its own text part of the observation tail, + so the transcript ledger keeps it verbatim: the whole resolution effort + stays legible across turns. + """ + + async def __call__(self, builder: PromptBuilder, state: State, ctx: ArtemisContext, **kwargs): + incident = getattr(state, "open_incident", None) + if isinstance(incident, dict) and incident.get("reason"): + builder.add_human_content( + render_execution_incident(incident, kwargs.get("steps") or []) + ) + return + closed = getattr(state, "last_closed_incident", None) + if isinstance(closed, dict) and closed.get("kind"): + builder.add_human_content(render_closed_incident(closed)) + + class CheckItemsExplainerPromptComponent(PromptComponent): """Behavioral guidance for check lines, rendered iff the CURRENT plan actually contains check lines (content-driven, not switch-driven: a resumed @@ -381,20 +581,25 @@ class CheckItemsExplainerPromptComponent(PromptComponent): return if not snapshot.all_check_items: return + setup = getattr(ctx, "execution_setup", None) + max_repairs = getattr(setup, "checkpoint_max_repairs", None) + if not isinstance(max_repairs, int): + max_repairs = 2 builder.add_human_content( "--- About the plan's check lines ---\n" - "The task plan declares `- verify:` / `- assert:` check lines." - " `verify:` lines are the acceptance criteria for their subgoal — use" - " them to confirm your work is complete, but the judgment is made by" - " an independent Checker: never declare a check passed yourself or" - " record conclusions on its behalf. `assert:` lines are test" - " assertions — take NO extra actions for them and never construct or" - " fake state to satisfy one; a failing assertion is a legitimate test" - " result. Check lines must not be deleted or reworded (deletions are" - " automatically restored by the system); keep them verbatim when" - " rewriting the plan — adding new ones is allowed. Simply mark" - " completions per the plan grammar as usual; checking runs" - " asynchronously in the background and does not block you." + "The task plan declares `- verify:` / `- assert:` check lines (see Task" + " Plan Grammar). Use `verify:` lines to confirm your work is complete," + " but never declare a check passed yourself or record conclusions on" + " its behalf. Take NO extra actions for `assert:` lines and never" + " construct or fake state to satisfy one. Check lines must not be" + " deleted or reworded (deletions are automatically restored by the" + " system); keep them verbatim when rewriting the plan — adding new ones" + " is allowed. Simply mark completions per the plan grammar as usual;" + " checking runs asynchronously in the background and does not block you." + f" A failed `verify:` reopens its milestone at most {max_repairs} time(s);" + " once that repair budget is exhausted, its standing `finding:` line" + " disappears and the failure stands as the recorded result — do not" + " keep repairing it." ) @@ -438,47 +643,50 @@ class BackgroundTasksPromptComponent(PromptComponent): class TaskPlanWarningPromptComponent(PromptComponent): + """Suggests a task_plan update after an action turn that did not touch it. + + Only the most recent step is inspected: if it executed a Turn-Ending + Action and no note tool wrote to ``task_plan``, a soft suggestion is + injected into the next turn. Pure diagnosis / waiting / observation turns + legitimately leave the plan alone and get no reminder. + """ + + NOTE_TOOLS = ("update_note", "save_note", "append_note") + + @classmethod + def _step_updated_task_plan(cls, step: dict) -> bool: + for tc in step.get("tool_calls", []): + if tc.get("name") not in cls.NOTE_TOOLS: + continue + args = tc.get("args", {}) + note_key = args.get("key") or args.get("name") + if note_key == "task_plan": + return True + return False + async def __call__(self, builder: PromptBuilder, state: State, ctx: ArtemisContext, **kwargs): steps = kwargs.get("steps", []) - if len(steps) >= 2: - last_two_steps = steps[-2:] - modified_in_last_two = False - for step in last_two_steps: - tool_calls = step.get("tool_calls", []) - for tc in tool_calls: - if tc.get("name") in [ - "update_note", - "save_note", - "append_note", - ]: - args = tc.get("args", {}) - note_key = args.get("key") or args.get("name") - if note_key == "task_plan": - modified_in_last_two = True - break - if modified_in_last_two: - break - - if not modified_in_last_two: - builder.add_human_content( - "\nReminder: You have not updated the task_plan for two" - " consecutive turns. Please check the latest progress," - " reflect on whether the task planning is detailed enough," - " and whether every pending item is listed as a subtask." - " Please update the completed tasks and pending tasks in" - " detail." - ) + if not steps: + return + last = steps[-1] + if not last.get("action_taken") or self._step_updated_task_plan(last): + return + builder.add_human_content( + "\nReminder: your last turn executed a Turn-Ending Action without" + " updating task_plan. If progress was made, record it now with" + " surgical `update_note` edits: mark what completed and add the" + " subtasks you are now pursuing." + ) class ToolLimitWarningPromptComponent(PromptComponent): async def __call__(self, builder: PromptBuilder, state: State, ctx: ArtemisContext, **kwargs): if getattr(state, "operator_tool_limit_exceeded", False): builder.add_human_content( - "\n Warning: You did not execute any screen interaction actions" - " in your last turn. Please re-examine the task goal and revise" - " your plan; your current approach may not be the correct path." - " Actively calling ask_diagnoser can help you diagnose the" - " issue." + "\nWarning: your last turn used up its tool-call budget without a" + " Turn-Ending Action. Re-examine the task goal and your plan;" + " the current approach may not be the right path. If the cause" + " is unclear, ask_diagnoser can help." ) @@ -566,11 +774,7 @@ class ScreenshotSimilarityPromptComponent(PromptComponent): # 4. Inject note if any identical screenshots are found if matched_step_nums: steps_str = ", ".join(matched_step_nums) - note_text = ( - f"Note: Screenshot in step {steps_str} seem to be identical to" - " current screen. This could be intended since not all actions" - " alter screens." - ) + note_text = f"Note: the screen is unchanged since step {steps_str} (pixel-identical)." builder.add_human_content(note_text) def _count_differing_pixels( @@ -648,8 +852,13 @@ class HistoricalStateHintPromptComponent(PromptComponent): if not getattr(transcript_cfg, "similarity_hint", True): return max_distance = int(getattr(transcript_cfg, "similarity_max_distance", 8)) - except Exception: - pass + except Exception as exc: + logger.debug( + "Transcript similarity config unavailable; using max_distance=%s: %s", + max_distance, + exc, + exc_info=True, + ) try: from artemis.utils.image_hash import dhash_hex, hamming_distance_hex diff --git a/artemis/agents/planner/planner.json b/artemis/agents/planner/planner.json index bbabdb2..2c9a9fb 100644 --- a/artemis/agents/planner/planner.json +++ b/artemis/agents/planner/planner.json @@ -6,9 +6,9 @@ "output_protocol_initial": "## Task Plan & Scratchpad Protocol\n\n1. **Articulate Thinking**: Begin by reasoning through the 5-step **Systematic Thinking Process** to establish your strategy, funnel approach, bounds, and pre-mortem checks.\n2. **Designate Notes in Milestones & Pre-generate Answer Cards**: If the task involves collecting findings, logging periodic monitoring events, recording candidates, or analytical summaries, designate clear, descriptive note names directly in the milestone descriptions (e.g., `candidates_list`, `monitoring_log`, `schedule_summary`). You can and should invoke the `save_note` tool to pre-generate these structured template files (answer card / scratchpad skeletons with Markdown headers, sections, or tables) before submitting `task_plan`. This provides downstream executors with a clear, ready-to-fill schema.\n3. **Submit Plan**: Submit your initial plan by invoking the `save_note` tool with `key='task_plan'`. Avoid writing the plan only in your text response, as it will only be recognized by the system if submitted via the tool.\n\n### Plan Formatting Rules\n- Format the plan strictly as a Markdown task list starting with `- [ ]` for uncompleted items.\n{{ plan_grammar }}\n- For linear macro-milestones: `- [ ] `\n- For bounded repetitive, iterative, or periodic polling milestones, tag the item with `[Loop]` and declare its Exit Boundary and Interval (e.g., `- [ ] [Loop] (Exit: ; Interval: )`).\n- For unbounded continuous monitoring that only the user can stop, tag the item with `[Loop:continuous]` instead (e.g., `- [ ] [Loop:continuous] (Interval: )`); never declare a self-serve exit condition for it.\n- **Strict Two-Tier Hierarchy & Loop Subtask Contract**:\n 1. **Subtask Nesting Rule**: ALL individual iteration instances, polling checks, candidate inspections, or baseline setups MUST be formatted as 2-space indented sub-tasks (` - [ ]`) directly nested under their parent `[Loop]` milestone.\n 2. **Mandatory First-Instance Initialization**: Every `[Loop]` milestone MUST be initialized with its first concrete child sub-task (e.g., ` - [ ] Inspect Candidate #1: ...` or ` - [ ] Polling Check #1 (Baseline & Initial Sync): ...`).\n 3. **No Flattened Cycles**: NEVER elevate individual checks, recurring polling cycles, or candidate inspections to top-level milestones outside the `[Loop]`. Top-level items are reserved exclusively for strategic macro-phases.\n- Ensure the content of the `task_plan` note contains only the task list, excluding introductions, explanations, conversational text, or headers.", "examples_initial": "## Master Example: Demonstrating the Complete Cognitive Paradigm\n\n**Goal**: \"Find and summarize all schedule arrangements and venue details for the upcoming November tech conference from my emails.\"\n\n### Naive / Incorrect Approaches:\n1. *Flawed Semantics & Too Granular*:\n- [ ] Tap search icon in Gmail\n- [ ] Type \"November\"\n- [ ] Open first email and tap back\n*(Fails: Micromanages atomic UI taps; assumes emails for November arrangements must have \"November\" in the title or be received in November)*\n2. *Vague & Unbounded Trap*:\n- [ ] Open email and check all emails for conference details\n*(Fails: Unbounded scan trap; causes infinite scrolling and step budget exhaustion on mobile)*\n\n---\n\n### Correct Approach (Full Cognitive Thinking & Robust Plan):\n\n#### Step-by-Step Thinking:\n1. **Intent & Semantic Expansion**:\n - *Core Objective*: Extract confirmed schedule dates, sessions, and venue info for the November tech conference.\n - *Semantic Insight*: Conference communications are often sent months in advance (e.g., ticket confirmations or registration emails in August/September). The subject line may say \"Global Developer Summit\", \"Tech Expo Agenda\", or \"Registration Confirmed\" rather than literally \"November conference\".\n - *Task Composition*: Linear setup (open email app and search) -> Iterative candidate inspection loop (examine each promising email) -> Final consolidation.\n2. **Visual Grounding**:\n - The device screenshot shows the Home screen with the Gmail app icon visible. The plan must start by opening the email client.\n3. **Tiered Strategy, Funnel Approach & Safeguards**:\n - *Tier 1 (Optimal)*: Use in-app search with rich semantic keywords (\"conference\", \"summit\", \"schedule\", \"agenda\", \"venue\", \"November\").\n - *Tier 2 (Fallback)*: If search is unavailable or yields zero results, fallback to chronological scanning of recent 3 months.\n - *Funnel (Broad Gathering -> Deep Inspection)*: Gather candidate emails from search results; do not discard candidates based solely on list previews. Open each candidate thread to inspect full body details.\n - *Feasibility Boundary*: Cap the candidate email inspection pool at top 15 emails to prevent infinite scrolling.\n4. **Risk Pre-mortem**:\n - *Risks*: Promotional marketing spam; conflicting drafts with preliminary dates; cancelled sessions.\n - *Defensive Check*: Instruct downstream executor to filter out promotional newsletters, verify session timestamps, and confirm final venue address.\n5. **Milestone Granularity & Scratchpad Blueprint**:\n - Focus milestones on business outcomes (search, inspect candidates, consolidate), avoiding micromanaged click-by-click steps.\n - Use indented sub-tasks to guide the loop of inspecting each candidate email, initializing with Item #1 and adding subsequent items as they are identified. This prevents the agent from losing state or re-inspecting the same items.\n - Designate note `november_conference_summary` as the destination for recording findings and pre-generate the structured template note.\n\n#### Tool Invocation:\n1. Pre-generate designated answer card note template:\n`save_note(key='november_conference_summary', content='# November Tech Conference Summary\\n\\n## Key Details\\n- Conference Name: \\n- Dates: \\n- Venue / Address: \\n\\n## Daily Schedule & Sessions\\n(To be populated during email inspection)\\n')`\n\n2. Submit milestone plan:\n`save_note(key='task_plan', content='- [ ] Open email application and search for candidate emails regarding the November tech conference (Search targeted keywords like \"conference\", \"summit\", \"agenda\", \"venue\"; if search is unavailable, fallback to scanning recent 3 months, capped at top 15 emails)\\n- [ ] [Loop] Inspect candidate emails and record verified schedule sessions and venue details into note `november_conference_summary` (Exit: All key conference details identified OR candidate pool exhausted / max 15 inspected)\\n - [ ] Inspect Candidate #1: Open candidate, filter out non-relevant content, check body for scheduling/venue, and log findings if present\\n- [ ] Review and consolidate all extracted information in `november_conference_summary` into a clear, finalized conference itinerary')`\n\n#### Content of `task_plan` Note:\n- [ ] Open email application and search for candidate emails regarding the November tech conference (Search targeted keywords like \"conference\", \"summit\", \"agenda\", \"venue\"; if search is unavailable, fallback to scanning recent 3 months, capped at top 15 emails)\n- [ ] [Loop] Inspect candidate emails and record verified schedule sessions and venue details into note `november_conference_summary` (Exit: All key conference details identified OR candidate pool exhausted / max 15 inspected)\n - [ ] Inspect Candidate #1: Open candidate, filter out non-relevant content, check body for scheduling/venue, and log findings if present\n- [ ] Review and consolidate all extracted information in `november_conference_summary` into a clear, finalized conference itinerary", "check_generation": "## Declaring Verification Standards (check lines)\nYou may attach check lines to the plan; an independent Checker judges them asynchronously.\n\n- **User-declared expectations become `- assert:` lines.** Whenever the user's goal states an explicit expectation (\"should show X\", \"must not crash\", \"the toast Y should appear\"), declare it as an assert line preserving the user's wording verbatim. Expectations about the whole run or the final state use the `@end` suffix (task-level lines at the end of the plan).\n- **`- verify:` lines only for milestones that genuinely need acceptance checks** — completion points of irreversible operations, or milestones that directly contribute to the final goal. Do NOT attach a verify line to every subgoal: restraint is an explicit requirement (checking cost scales with the number of check lines).\n- **Do not fabricate assertions** for subgoals that have nothing verifiable. Rewrite transient expectations as persistently probeable state wherever possible (e.g., \"alarm exists in the alarm list\" instead of \"a toast flashed\") — evidence for transient moments only exists if the execution history happened to capture it.\n- **Ordering requirements are post-hoc.** If the user demands \"A must be confirmed before B runs\", still generate the assertion preserving their semantics, but annotate in the plan that ordering is verified retroactively from history — the system performs post-hoc audits and CANNOT block B from executing. Never silently promise enforcement that does not exist.", - "check_audit": "- **Check Standard Protection**: Check lines (`- verify:` / `- assert:`) declared in the plan are the task's verification standards. REJECT any proposed plan that deletes, weakens, or rewords existing check lines — softening a declared standard is never an acceptable adaptation. Adding new check lines is allowed.", - "role_validator": "## Role\nYou are the Task Plan Validator. The Operator has attempted to modify the top-level task plan. Your job is to ensure the proposed new plan still aligns with the user's original intent and is viable.", - "validator_guidelines": "## Validation Principles\n- **Goal Alignment**: Ensure the new plan ultimately accomplishes the `Initial Goal` requested by the user. If the Operator has abandoned the core objective or fundamentally altered it, you must reject the plan.\n- **Logical Coherence**: Verify that the proposed milestones form a logical, achievable sequence to reach the goal. Support valid ongoing iteration/polling subgoals and trigger branches without misconstruing periodic checks as infinite loops. Reject the plan if it introduces contradictory steps, unhandled deadlocks, or obvious logical fallacies.\n{{ plan_grammar }}\n- **Loop & Monitoring Protection**: If the existing plan contains an active `[Loop]` or `[Loop:continuous]` milestone, you MUST REJECT any proposed plan that deletes the milestone or removes its loop tag, as well as any plan that marks a `[Loop:continuous]` continuous monitoring milestone as completed (`[x]`) without an explicit external user stop signal.\n- **Adaptive Acceptance**: The Operator operates in a dynamic environment and may encounter unexpected UI states or dynamic event triggers. You should approve alternative strategies, extra intermediate steps, loop status tracking, or necessary workarounds as long as they serve the primary goal effectively.\n- **Constructive Feedback**: If you must reject the plan, provide clear, objective, and actionable feedback explaining the logical flaw or goal deviation, and suggest how the Operator should correct the plan." + "check_audit": "- **Check Standard Protection**: Check lines (`- verify:` / `- assert:`) declared in the plan are the task's verification standards. FLAG any proposed plan that deletes, weakens, or rewords existing check lines — softening a declared standard is never an acceptable adaptation. Adding new check lines is allowed.", + "role_validator": "## Role\nYou are the Task Plan Reviewer, a fast advisory second opinion. The Operator has modified the top-level task plan and the change is already applied. Your job is to spot a concrete concern — goal drift, a broken loop contract, a weakened check standard, a logical dead end — and, if there is one, state it with its reason so the Operator can decide. You never block or roll back anything.", + "validator_guidelines": "## Validation Principles\n- **Goal Alignment**: Ensure the new plan ultimately accomplishes the `Initial Goal` requested by the user. If the Operator has abandoned the core objective or fundamentally altered it, flag the plan.\n- **Logical Coherence**: Verify that the proposed milestones form a logical, achievable sequence to reach the goal. Support valid ongoing iteration/polling subgoals and trigger branches without misconstruing periodic checks as infinite loops. Flag the plan if it introduces contradictory steps, unhandled deadlocks, or obvious logical fallacies.\n{{ plan_grammar }}\n- **Loop & Monitoring Protection**: If the existing plan contains an active `[Loop]` or `[Loop:continuous]` milestone, you MUST FLAG any proposed plan that deletes the milestone or removes its loop tag, as well as any plan that marks a `[Loop:continuous]` continuous monitoring milestone as completed (`[x]`) without an explicit external user stop signal.\n- **Adaptive Acceptance**: The Operator operates in a dynamic environment and may encounter unexpected UI states or dynamic event triggers. You should approve alternative strategies, extra intermediate steps, loop status tracking, or necessary workarounds as long as they serve the primary goal effectively.\n- **Advisory Verdict & Constructive Feedback**: Your verdict is advisory — the change stays applied whether or not you approve it. Approve unless you have a concrete, evidence-backed concern; a vague unease is not a concern. If you flag the plan, give one or two sentences naming the flaw or goal deviation and the correction you suggest, concrete enough for the Operator to weigh it against its own observations." }, "modes": { "initial_plan": { diff --git a/artemis/agents/planner/planner.py b/artemis/agents/planner/planner.py index 61d0be4..98f91f9 100644 --- a/artemis/agents/planner/planner.py +++ b/artemis/agents/planner/planner.py @@ -122,13 +122,17 @@ class ValidationResult(BaseModel): model_config = {"ignored_types": (CyFunctionDetector,)} is_approved: bool = Field( description=( - "True if the new plan is acceptable and aligns with the initial goal, False otherwise." + "True if the new plan still serves the initial goal and is coherent." + " False if you have a concrete concern (goal drift, broken loop" + " contract, weakened check standard, logical dead end)." ) ) feedback: str = Field( description=( - "If rejected, provide constructive feedback explaining why and what" - " to do instead. If approved, can be empty." + "If not approved: one or two sentences naming the concern and the" + " suggested correction. This is advisory feedback shown to the" + " Operator, not a rejection — the plan change stays applied." + " If approved, leave empty." ) ) @@ -185,7 +189,9 @@ async def run_async_planner_validation( HumanMessage(content=human_message), ] - llm = get_llm(ctx=ctx, name="planner").with_structured_output(ValidationResult) + # Advisory review on the lightweight judge node (shared default with + # the pixel safety net), never the heavyweight Planner model. + llm = get_llm(ctx=ctx, name="planner_validation").with_structured_output(ValidationResult) result: ValidationResult = await invoke_llm_with_timeout_message(llm.ainvoke(messages)) diff --git a/artemis/agents/summarizer/summarizer.md b/artemis/agents/summarizer/summarizer.md index 88c6e68..8a9d6d1 100644 --- a/artemis/agents/summarizer/summarizer.md +++ b/artemis/agents/summarizer/summarizer.md @@ -8,13 +8,13 @@ You are the Summarizer Agent for a mobile automation system. Your task is to sum # CORE PHILOSOPHY & RULES -1. **Perspective**: You MUST write the summary from the Operator's first-person perspective using "I" (e.g., "I intended to...", "I clicked..."). NEVER use third-person terms like "The operator" or "The agent", nor second-person terms like "You" when referring to the Operator. For other agents (e.g., Failure Analyzer, Explorer), you can use their names. +1. **Perspective**: You MUST write the summary from the Operator's first-person perspective using "I" (e.g., "I intended to...", "I clicked..."). NEVER use third-person terms like "The operator" or "The agent", nor second-person terms like "You" when referring to the Operator. For other agents (e.g., Explorer, Diagnoser), you can use their names. 2. **Zero Subjective Validation**: Absolutely avoid declaring semantic success, completion, or failure. In historical compression, any subjective validation will cause future planners to falsely believe a goal was definitively met or permanently broken. - **BANNED WORDS**: "successfully", "completed", "entered", "navigated to", "failed", "unsuccessful", "could not", "achieved". 3. **Focus on Intent & Strategy**: The specific physical action (exact coordinates, swipes) is now recorded automatically. Focus primarily on the high-level Intent and reasoning. If you realize the Operator is on the wrong path and needs to backtrack or pivot, describe this as a shift in strategy. 4. **Intended Context Transitions**: Describe the intended navigation path ("From Where -> Intended Where"). Do not declare that the transition actually happened. (e.g., "From the Home screen, I intended to open the Network Settings view"). 5. **Preserve Verifications & Iteration Progress**: If the Operator verified any state in this step, processed a specific candidate item/polling round, or explicitly mentioned what prerequisites or items are still missing, you MUST succinctly preserve these concrete progress details and verified items in the summary. -6. **Objective Intervention Logs**: If the Failure Analyzer intervened, faithfully summarize what triggered the intervention and what the analyzer executed, without judging the outcome. +6. **Objective Incident Logs**: If the safety net intercepted the action, the device rejected it, or a fast-action burst aborted midway (an `[Result]` line starting with "Error:" / an execution incident), faithfully record what was blocked and the stated reason, without judging whether the Operator's next move will resolve it. # LOOP & STAGNATION DETECTION Use the `RECENT 10 STEPS HISTORY` to intelligently detect loops or stagnation: @@ -30,5 +30,6 @@ You will receive: - **CHRONOLOGICAL STEP TRACE (CURRENT STEP)**: The step-by-step chronological history of the current execution turn, including: - `[Operator Monologue] / [Operator Native Thought]`: The operator's planning, intentions, progress counting, reasoning, verified states, and explicit mentions of missing prerequisites. - `[Operator Tool Call] / [Operator Final Action]`: The actual action(s) planned and executed. + - `[Planned Fast-Action Burst]`: A multi-action turn executed back to back without the safety net, with each member's outcome. - `[Pre-Execution Safety Net]`: Validation checks prior to action execution. - - `[Failure Analyzer Recovery Loop]`: Interventions, thoughts, auxiliary tool calls, and outcomes of the Failure Analyzer. + - `[Result]`: The Validator's execution outcome; an "Error:" line here is an execution incident the Operator must resolve. diff --git a/artemis/agents/summarizer/summarizer.py b/artemis/agents/summarizer/summarizer.py index 2807e3f..e139010 100644 --- a/artemis/agents/summarizer/summarizer.py +++ b/artemis/agents/summarizer/summarizer.py @@ -101,9 +101,7 @@ class SummarizerNode: exec_outcome=exec_outcome, data_engine_step_id=step_id, ) - logger.info( - f"Dispatched visual transition summary for step {step_number} ({step_id})" - ) + logger.info(f"Dispatched visual transition summary for step {step_number} ({step_id})") def _read_step_image(self, step_number: int | None, which: str) -> bytes | None: if not isinstance(step_number, int): diff --git a/artemis/agents/validator/categories.py b/artemis/agents/validator/categories.py new file mode 100644 index 0000000..5e8f09a --- /dev/null +++ b/artemis/agents/validator/categories.py @@ -0,0 +1,33 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Safety-net failure taxonomy shared by the Validator's precondition gates. + +The categories are produced by the XML gate (``precondition_xml``) and the +pixel gate (``precondition_pixel``) and consumed by the execution loop when it +opens an :mod:`artemis.agents.validator.incidents` record for the Operator. +""" + +from enum import StrEnum + + +class ValidationErrorCategory(StrEnum): + NONE = "none" + TARGET_DISAPPEARED = "target_disappeared" + TARGET_SHIFTED = "target_shifted" + TARGET_OCCUPIED = "target_occupied" + PIXEL_TARGET_DISAPPEARED = "pixel_target_disappeared" + PIXEL_BYPASSED = "pixel_bypassed" + XML_BYPASSED = "xml_bypassed" + GENERAL = "general" diff --git a/artemis/agents/validator/execution_loop.py b/artemis/agents/validator/execution_loop.py index 08d17c8..b6d5123 100644 --- a/artemis/agents/validator/execution_loop.py +++ b/artemis/agents/validator/execution_loop.py @@ -14,9 +14,21 @@ """Per-turn action execution loop for the Validator. -Orchestrates, for each Operator decision: precondition safety-net checks -(XML-first with pixel fallback), local execution with retries, screenshot and -trace bookkeeping, and FailureAnalyzer-driven local repair. +Two execution tiers, selected by how many turn-ending actions the Operator +emitted in the turn: + +* **Vetted single action** (one action): the pre-execution Safety Net runs + first (XML-first with pixel fallback), then the action executes locally with + a short retry loop. +* **Fast-action burst** (two or more actions): the actions fire back to back + with no safety net, no retries and no screenshots in between. The burst is + the Operator's tool against turn latency (transient menus, toasts, control + bars); the Operator owns the risk. The first failure aborts the rest. + +Either way, a failure does not spawn a repair agent. The loop opens an +*execution incident* (:mod:`artemis.agents.validator.incidents`) that rides in +graph state and in the step's execution report, and the Operator resolves it +on its next turn with the live screen in front of it. Extracted from ``validator.py``; the public entry point remains ``ValidatorNode.__call__``, which delegates here. All patchable seams @@ -27,21 +39,26 @@ are dispatched through the node so existing test patches keep working. import asyncio import base64 from dataclasses import dataclass -from pathlib import Path import time import uuid from uuid import UUID -from artemis.agents.validator.failure_analyzer import ( - FailureAnalyzer, - ValidationErrorCategory, +from artemis.agents.validator.categories import ValidationErrorCategory +from artemis.agents.validator.incidents import ( + KIND_EXEC_ERROR, + KIND_SAFETY_NET, + open_incident, ) from artemis.data_engine.trace import CURRENT_TRACE_ID from artemis.graph.state import State from artemis.utils.logger import get_logger +from artemis.utils.task_tree import format_action_clean logger = get_logger(__name__) +#: ``attempts`` marker for actions a burst never reached. +BURST_SKIPPED_MARKER = "Skipped (burst aborted)" + @dataclass class _ActionOutcome: @@ -51,6 +68,7 @@ class _ActionOutcome: success: bool = False error_msg: str = "" error_category: ValidationErrorCategory = ValidationErrorCategory.GENERAL + intercepted: bool = False post_screenshot_b64: str | None = None @@ -109,19 +127,16 @@ async def _run_precondition_gate( session, action_item, pre_screenshot_b64, original_coords, state=state ) if pixel_passed and pixel_category == ValidationErrorCategory.NONE: - logger.success( - "Pixel-based validation fallback PASSED! Overriding XML validation failure." - ) + logger.success("Pixel-based validation fallback PASSED! Overriding XML validation failure.") return True, ValidationErrorCategory.NONE, "" return validation_passed, validation_category, validation_error async def _attempt_local_execution( - node, session, action_item: dict, action_name, attempts_log: list + node, session, action_item: dict, action_name, attempts_log: list, *, max_local_retries: int ) -> tuple[bool, str]: """Executes the action locally with a short retry loop.""" - max_local_retries = 1 if action_name == "launch_app" else 2 success = False error_msg = "" for attempt in range(max_local_retries): @@ -157,43 +172,57 @@ async def _capture_live_screenshot(session, context_desc: str) -> str | None: async def _process_action( - node, state: State, session, action_item: dict, action_name, pre_screenshot_b64: str + node, + state: State, + session, + action_item: dict, + action_name, + pre_screenshot_b64: str, + *, + burst: bool, ) -> _ActionOutcome: - """Runs precondition checks and execution for one action item.""" + """Runs precondition checks (vetted tier only) and execution for one action item.""" action_item = dict(action_item) # Make a copy to avoid mutating original state actions outcome = _ActionOutcome(action_item=action_item) attempts_log = [] - # 1. Pre-execution validation (Safety Net) - original_coords = list(action_item.get("coordinates") or []) - ( - validation_passed, - validation_category, - validation_error, - ) = await _run_precondition_gate( - node, session, action_item, state, pre_screenshot_b64, original_coords - ) - - if not validation_passed: - outcome.success = False - outcome.error_msg = f"Pre-execution validation failed: {validation_error}" - outcome.error_category = validation_category - attempts_log.append(outcome.error_msg) - - # Capture the live mismatch screenshot to provide context for FailureAnalyzer - outcome.post_screenshot_b64 = await _capture_live_screenshot( - session, "live screenshot for failure analysis" + validation_passed = True + if not burst: + # 1. Pre-execution validation (Safety Net) + original_coords = list(action_item.get("coordinates") or []) + ( + validation_passed, + validation_category, + validation_error, + ) = await _run_precondition_gate( + node, session, action_item, state, pre_screenshot_b64, original_coords ) - else: - # 2. Local execution attempts + if not validation_passed: + outcome.success = False + outcome.intercepted = True + outcome.error_msg = f"Pre-execution validation failed: {validation_error}" + outcome.error_category = validation_category + attempts_log.append(outcome.error_msg) + + if validation_passed: + # 2. Local execution: a vetted action gets a short retry loop, a burst + # member fires exactly once (retries would break the burst's timing). + if burst: + max_local_retries = 1 + else: + max_local_retries = 1 if action_name == "launch_app" else 2 outcome.success, outcome.error_msg = await _attempt_local_execution( - node, session, action_item, action_name, attempts_log + node, + session, + action_item, + action_name, + attempts_log, + max_local_retries=max_local_retries, ) - if not outcome.success and not outcome.post_screenshot_b64: - logger.info("Action failed, capturing failure screenshot...") - outcome.post_screenshot_b64 = await _capture_live_screenshot( - session, "failure screenshot" - ) + + if not outcome.success: + logger.info("Action failed, capturing failure screenshot...") + outcome.post_screenshot_b64 = await _capture_live_screenshot(session, "failure screenshot") # Enrich the executed action record if len(attempts_log) > 1 or not outcome.success: @@ -241,6 +270,8 @@ def _record_action_end( parent_id, action_trace_id, start_time: float, + *, + burst: bool, ): duration = time.time() - start_time if ctx.data_engine and step_id: @@ -255,6 +286,7 @@ def _record_action_end( "post_screenshot": post_image_name, "timestamp": time.time(), "relative_time": relative_time, + "burst": burst, }, status="success" if outcome.success else "failed", duration=duration, @@ -264,55 +296,61 @@ def _record_action_end( ) -def _reload_post_repair_screenshot( - ctx, state: State, last_screenshot_b64: str, last_screenshot_name: str | None -) -> tuple[str, str | None]: - """Reloads the latest screenshot updated by FailureAnalyzer during repair.""" +def _normalize_point(ctx, point) -> list[int] | None: + """Pixel [x, y] -> normalized 0-1000 [x, y] using the device dims.""" try: - screenshot_path = getattr(state, "latest_screenshot", None) - if screenshot_path: - if Path(screenshot_path).exists(): - with open(screenshot_path, "rb") as f: - last_screenshot_b64 = base64.b64encode(f.read()).decode("utf-8") - if ctx.data_engine: - last_screenshot_name = ctx.data_engine.get_or_create_image( - base64.b64decode(last_screenshot_b64) - ) - logger.info(f"Validator reloaded post-repair screenshot from: {screenshot_path}") - except Exception as e: - logger.error(f"Failed to reload post-repair screenshot: {e}") - return last_screenshot_b64, last_screenshot_name + x, y = int(point[0]), int(point[1]) + except (TypeError, ValueError, IndexError, KeyError): + return None + width = getattr(getattr(ctx, "device", None), "device_width", None) or 1080 + height = getattr(getattr(ctx, "device", None), "device_height", None) or 2400 + return [ + max(0, min(1000, round(x * 1000 / width))), + max(0, min(1000, round(y * 1000 / height))), + ] -async def _analyze_failure( - node, - state: State, - outcome: _ActionOutcome, - *, - decision_screenshot_b64, - decision_screenshot_name, - post_image_name, - executed_actions, - unexecuted_actions, -) -> dict: - """Triggers the FailureAnalyzer for a failed action and records its analysis.""" - logger.warning(f"Action failed: {outcome.action_item}. Triggering failure analysis.") +def _incident_evidence(ctx, outcome: _ActionOutcome) -> dict: + """Structured facts the safety net attached to the action item, normalized.""" + raw = outcome.action_item.pop("safety_net_evidence", None) or {} + evidence: dict = {} + if raw.get("new_center"): + normalized = _normalize_point(ctx, raw["new_center"]) + if normalized: + evidence["new_location"] = normalized + if raw.get("new_bounds") is not None: + evidence["new_bounds"] = raw["new_bounds"] + if raw.get("occupant"): + evidence["occupant"] = raw["occupant"] + if raw.get("occupant_bounds") is not None: + evidence["occupant_bounds"] = raw["occupant_bounds"] + return evidence - analyzer = FailureAnalyzer(node.ctx) - analysis_result = await analyzer.analyze( - state, - outcome.action_item, - outcome.error_msg, - pre_screenshot=decision_screenshot_b64, - post_screenshot=outcome.post_screenshot_b64, - pre_screenshot_name=decision_screenshot_name, - post_screenshot_name=post_image_name, - executed_actions=executed_actions, - unexecuted_actions=unexecuted_actions, - error_category=outcome.error_category, + +def _current_step_number(ctx, state: State) -> int | None: + step_id = getattr(state, "current_step_id", None) + if ctx.data_engine and step_id: + try: + return ctx.data_engine.get_step_number(UUID(step_id)) + except Exception: + return None + return None + + +def _build_incident(ctx, state: State, outcome: _ActionOutcome, *, index: int, total: int) -> dict: + step_number = _current_step_number(ctx, state) + return open_incident( + previous=getattr(state, "open_incident", None), + kind=KIND_SAFETY_NET if outcome.intercepted else KIND_EXEC_ERROR, + category=outcome.error_category, + reason=outcome.error_msg, + action_item=outcome.action_item, + action_description=format_action_clean(outcome.action_item), + action_index=index, + burst_size=total, + step_number=step_number, + evidence=_incident_evidence(ctx, outcome), ) - outcome.action_item["repair"] = analysis_result.get("analysis", "No analysis provided.") - return analysis_result async def run_validation_loop(node, state: State) -> dict: @@ -328,8 +366,14 @@ async def run_validation_loop(node, state: State) -> dict: return {} execution: list[dict] = [] - failed_action = None actions_to_execute = list(actions) + total_actions = len(actions_to_execute) + burst = total_actions > 1 + if burst: + logger.info( + f"Fast-action burst: {total_actions} actions will fire back to back" + " without the safety net." + ) session = await node._get_mcp_session() @@ -338,15 +382,15 @@ async def run_validation_loop(node, state: State) -> dict: session, state ) # Preserve the turn-initial screenshot (what the operator saw when making decisions) - decision_screenshot_b64 = last_screenshot_b64 decision_screenshot_name = last_screenshot_name except Exception: return {} step_id = UUID(state.current_step_id) if state.current_step_id else None - analysis_result: dict = {} + incident: dict | None = None success = False + index = 0 while actions_to_execute: action_item = actions_to_execute.pop(0) @@ -363,16 +407,14 @@ async def run_validation_loop(node, state: State) -> dict: try: outcome = await _process_action( - node, state, session, action_item, action_name, last_screenshot_b64 + node, state, session, action_item, action_name, last_screenshot_b64, burst=burst ) success = outcome.success execution.append(outcome.action_item) post_image_name = None if outcome.post_screenshot_b64: - post_image_name = _absorb_post_screenshot( - ctx, state, outcome.post_screenshot_b64 - ) + post_image_name = _absorb_post_screenshot(ctx, state, outcome.post_screenshot_b64) last_screenshot_b64 = outcome.post_screenshot_b64 if post_image_name: last_screenshot_name = post_image_name @@ -386,48 +428,32 @@ async def run_validation_loop(node, state: State) -> dict: parent_id, action_trace_id, start_time, + burst=burst, ) finally: CURRENT_TRACE_ID.reset(token) if not success: - failed_action = outcome.action_item - - analysis_result = await _analyze_failure( - node, - state, - outcome, - decision_screenshot_b64=decision_screenshot_b64, - decision_screenshot_name=decision_screenshot_name, - post_image_name=post_image_name, - executed_actions=execution[:-1], - unexecuted_actions=list(actions_to_execute), + incident = _build_incident(ctx, state, outcome, index=index, total=total_actions) + logger.warning( + f"Action failed: {outcome.action_item}. Opened execution incident" + f" ({incident['kind']}/{incident['category']}," + f" consecutive={incident['consecutive_failures']}) for the Operator." ) - - if analysis_result.get("status") == "fixed": - logger.success("Failure repaired locally!") - actions_to_execute = [] - last_screenshot_b64, last_screenshot_name = _reload_post_repair_screenshot( - ctx, state, last_screenshot_b64, last_screenshot_name - ) - continue - else: - break + break + index += 1 # Append any skipped actions to the execution log for skipped in actions_to_execute: skipped_copy = dict(skipped) - skipped_copy["attempts"] = ["Skipped"] + skipped_copy["attempts"] = [BURST_SKIPPED_MARKER if burst else "Skipped"] execution.append(skipped_copy) report = { "execution": execution, - "status": ( - "success" - if success or (failed_action and analysis_result.get("status") == "fixed") - else "failed" - ), - "repair_status": (analysis_result.get("status") if failed_action else None), + "status": "success" if success else "failed", + "burst": burst, + "incident": incident, } # Determine if a distinct post-action screenshot was captured @@ -442,4 +468,15 @@ async def run_validation_loop(node, state: State) -> dict: step_id, report, post_image_name=distinct_post_image_name ) - return {"last_execution_result": report} + # A successful turn closes whatever incident was open (handing the Operator + # a one-turn CLOSED notice); a failed one keeps and escalates it. + closed = None + previous = getattr(state, "open_incident", None) + if success and isinstance(previous, dict) and previous.get("kind"): + closed = dict(previous) + closed["closed_at_step"] = _current_step_number(ctx, state) + return { + "last_execution_result": report, + "open_incident": incident, + "last_closed_incident": closed, + } diff --git a/artemis/agents/validator/failure_analyzer.md b/artemis/agents/validator/failure_analyzer.md deleted file mode 100644 index 9d7e5b4..0000000 --- a/artemis/agents/validator/failure_analyzer.md +++ /dev/null @@ -1,25 +0,0 @@ -# Identity & Goal -You are a specialized error recovery agent. You will be provided with a failed action. Your ONLY task is to repair this specific failure and restore the system state so the main system can resume. - -# Context -- **Initial Goal**: {{ initial_goal }} -- **History & Plan**: -{{ plan_and_history }} - -# Core Workflow -1. **Analyze**: Compare the two states provided to you: - - **Decision State (`Screenshot Seen During System Decision`)**: This is the screen the system saw when it originally planned the action. It shows the target element successfully located. - - **Failed State (`Latest Screenshot (Failed State)`)**: This is the live screen captured at the moment you were summoned. It shows the current state where the target is missing or unresponsive. - Your goal in this comparison is to: - - Read the Operator's thought process in the decision loop of the failed step to understand what they were originally trying to accomplish with this failed action. - - Understand the expected target layout. - - Identify the cause of the failure based on the error message and the visual mismatch. -2. **Recover & Execute**: Restore the target state and execute the failed action. - - **Flexibility**: Focus on outcome over form. Take alternative/adaptive steps (e.g., scroll, navigate, different click paths) if original coordinates are invalid. -3. **Report**: Call `report_failure_analysis` to conclude: - - `status="fixed"`: Successfully executed the failed action, reached the destination state, or false alarm. - - `status="cannot_fix"`: Target is unrecoverable, the app is broken, or the 15-step budget is exceeded. - - `analysis`: Summarize the root cause, the fix applied, and explicitly confirming that control is handed back to the main system. - -# Constraints -The most recent step (Step {{ failed_step_number }}: {{ failed_action_description }}) failed to execute. You must resolve the issue and report the results in 15 interactive steps or fewer. Now please begin the repair analysis. Please note that during the **Recover & Execute** phase, you only need to resolve the currently failed step; do not attempt to execute any subsequent steps or complete the overall goal yourself. Once the failure is resolved, please immediately enter the **Report** phase and return control to the system. diff --git a/artemis/agents/validator/failure_analyzer.py b/artemis/agents/validator/failure_analyzer.py deleted file mode 100644 index 2938830..0000000 --- a/artemis/agents/validator/failure_analyzer.py +++ /dev/null @@ -1,642 +0,0 @@ -# Copyright 2026 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Universal Multi-Model Failure Analyzer for Artemis. - -Analyzes execution failures and performs autonomous self-healing across -Google Gemini, OpenAI GPT-4o/o3, Anthropic Claude 3.5/3.7, and OpenRouter endpoints. -""" - -import asyncio -from enum import StrEnum -import json -from pathlib import Path -from typing import Any -import uuid - -from jinja2 import Template -from langchain_core.messages import ( - BaseMessage, - HumanMessage, - SystemMessage, - ToolMessage, -) - -from artemis.agents.validator.tool_declarations import ( - ASK_EXPLORER_TOOL, - CLICK_SEQUENCE_TOOL, - MobileActionExecutor, # Legacy shims below; the loop itself uses McpActionExecutor. - ToolDeclaration, - VALIDATOR_TOOLS_DECLARATION, - capture_screenshot_and_parse_ui, - prune_intermediate_screenshots, -) -from artemis.context import ArtemisContext -from artemis.controllers.unified_controller import UnifiedMobileController -from artemis.data_engine.trace import trace -from artemis.graph.state import State -from artemis.mcp.action_executor import McpActionExecutor -from artemis.services.llm import get_llm -from artemis.utils.logger import get_logger -from artemis.utils.notes import get_note_file_path -from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml -from artemis.memory.context_policy import build_history_for -from artemis.utils.task_tree import ( - format_action_clean, - get_active_subgoal_hashes, -) -from artemis.utils.visualization import format_minimal_list_with_elements - - -class ValidationErrorCategory(StrEnum): - NONE = "none" - TARGET_DISAPPEARED = "target_disappeared" - TARGET_SHIFTED = "target_shifted" - TARGET_OCCUPIED = "target_occupied" - PIXEL_TARGET_DISAPPEARED = "pixel_target_disappeared" - PIXEL_BYPASSED = "pixel_bypassed" - XML_BYPASSED = "xml_bypassed" - GENERAL = "general" - - -logger = get_logger(__name__) - - -# ============================================================================== -# Backward-Compatibility Helper Aliases -# ============================================================================== - - -async def _capture_screenshot_and_parse_ui( - ctx: ArtemisContext, - state: State, - controller: UnifiedMobileController, - skip_settling: bool = False, -) -> tuple[str | None, bytes | None, str | None]: - return await capture_screenshot_and_parse_ui( - ctx, state, controller, skip_settling=skip_settling - ) - - -async def _exec_click( - target: list[int], - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, - times: int = 1, - delay_ms: int = 100, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_click( - target, state, times=times, delay_ms=delay_ms - ) - - -async def _exec_click_sequence( - sequence: list[int | list[int]], - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_click_sequence(sequence, state) - - -async def _exec_long_press( - target: list[int], - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, - duration_ms: int = 1000, - duration: int | None = None, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_long_press( - target, state, duration_ms=duration_ms, duration=duration - ) - - -async def _exec_input_text( - text: str, - target: list[int] | None, - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, - clear_exist: bool = True, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_input_text( - text, target, state, clear_exist=clear_exist - ) - - -async def _exec_swipe( - action: str | list[int], - duration: int = 400, - state: State | None = None, - controller: UnifiedMobileController | None = None, - ctx: ArtemisContext | None = None, - duration_ms: int | None = None, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_swipe( - action, duration=duration, state=state, duration_ms=duration_ms - ) - - -async def _exec_press_key( - key: str, - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_press_key(key, state) - - -async def _exec_manage_app( - action: str, - app_name: str, - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_manage_app(action, app_name, state) - - -async def _exec_wait_for_delay( - time_in_ms: int, - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_wait_for_delay(time_in_ms, state) - - -async def _exec_wait_for_text( - text: str, - wait_state: str | None, - timeout_ms: int | None, - state: State, - controller: UnifiedMobileController, - ctx: ArtemisContext, -) -> tuple[str, bytes | None, str | None, str | None]: - return await MobileActionExecutor(ctx, controller).exec_wait_for_text( - text, wait_state, timeout_ms, state - ) - - -async def _exec_read_note( - key: str, - ctx: ArtemisContext, - start_line: int | None = None, - end_line: int | None = None, -) -> str: - return await MobileActionExecutor(ctx).exec_read_note( - key, start_line=start_line, end_line=end_line - ) - - -async def _exec_list_notes(ctx: ArtemisContext) -> str: - return await MobileActionExecutor(ctx).exec_list_notes() - - -async def _exec_ask_explorer( - query: str, - context_feedback: str | None, - state: State, - ctx: ArtemisContext, - version: str = "flash", -) -> str: - return await MobileActionExecutor(ctx).exec_ask_explorer( - query, context_feedback, state, version=version - ) - - -def _get_ask_explorer_tool_declaration() -> ToolDeclaration: - return ASK_EXPLORER_TOOL - - -class FailureAnalysisStrategy: - """Base strategy class for FailureAnalyzer strategies.""" - - def get_prompt_template_name(self) -> str: - return "failure_analyzer.md" - - def get_tools(self) -> list: - return VALIDATOR_TOOLS_DECLARATION - - def required_tools(self) -> frozenset[str]: - """Tools this strategy's prompt depends on structurally. - - A strategy whose prompt is built around a tool (rather than merely listing it) - must declare it here; strategy selection falls back to the base strategy when - the current backend does not provide every required tool, so the dependent - prompt is never rendered against a backend that cannot satisfy it. - """ - return frozenset() - - def get_user_message_suffix(self, pre_screenshot: bool, post_screenshot: bool) -> str: - suffix = ( - "Your objective is:\n" - "1. Analyze why the action planned in the failed step failed, using the failed step's" - " details, the decision screenshot (Screenshot Seen During System Decision), the" - " latest failed screenshot (Latest Screenshot (Failed State)), and their corresponding" - " UI Element lists.\n" - "2. Resolve any timing, state mismatches (e.g., re-triggering disappeared elements), or" - " environmental obstacles using your execution tools.\n" - "3. Crucially: You MUST execute the failed action yourself using your execution tools" - " to completion.\n" - "4. Once you have successfully executed the repair and the failed action, call the" - " `report_failure_analysis` tool with status='fixed'.\n" - "5. If you are not confident or cannot resolve the failure, call" - " `report_failure_analysis` with status='cannot_fix'." - ) - if pre_screenshot and post_screenshot: - suffix += ( - "\nI have attached the screenshot seen when the system made its" - " decision (Screenshot Seen During System Decision) and the" - " latest screenshot in the failed state (Latest Screenshot" - " (Failed State))." - ) - return suffix - - -class TargetDisappearedStrategy(FailureAnalysisStrategy): - """Specialized strategy for target disappeared errors.""" - - def get_prompt_template_name(self) -> str: - return "target_disappeared_analyzer.md" - - def get_tools(self) -> list: - tools = list(VALIDATOR_TOOLS_DECLARATION) - tools.insert(1, CLICK_SEQUENCE_TOOL) - tools.append(ASK_EXPLORER_TOOL) - return tools - - def required_tools(self) -> frozenset[str]: - # The whole prompt is built around atomic chained execution to defeat turn - # latency, which *is* click_sequence. - return frozenset({"click_sequence"}) - - def get_user_message_suffix(self, pre_screenshot: bool, post_screenshot: bool) -> str: - return "" - - -class PixelTargetDisappearedStrategy(FailureAnalysisStrategy): - """Specialized strategy for pixel-level target disappeared errors.""" - - def get_prompt_template_name(self) -> str: - return "pixel_target_disappeared_analyzer.md" - - def get_tools(self) -> list: - tools = list(VALIDATOR_TOOLS_DECLARATION) - tools.insert(1, CLICK_SEQUENCE_TOOL) - tools.append(ASK_EXPLORER_TOOL) - return tools - - def required_tools(self) -> frozenset[str]: - return frozenset({"click_sequence"}) - - def get_user_message_suffix(self, pre_screenshot: bool, post_screenshot: bool) -> str: - return "" - - -class FailureAnalyzer: - """Universal Multi-Model Failure Analyzer.""" - - def __init__(self, ctx: ArtemisContext): - self.ctx = ctx - self.max_iterations = 15 - - def _select_strategy( - self, - error_category: ValidationErrorCategory, - available_tools: frozenset[str] | None = None, - ) -> FailureAnalysisStrategy: - if error_category == ValidationErrorCategory.PIXEL_TARGET_DISAPPEARED: - logger.info("Routing failure analysis to specialized PixelTargetDisappearedStrategy.") - strategy: FailureAnalysisStrategy = PixelTargetDisappearedStrategy() - elif error_category == ValidationErrorCategory.TARGET_DISAPPEARED: - logger.info("Routing failure analysis to specialized TargetDisappearedStrategy.") - strategy = TargetDisappearedStrategy() - else: - logger.info("Routing failure analysis to DefaultAnalysisStrategy.") - strategy = FailureAnalysisStrategy() - - # Capability gating: a strategy and its prompt are one unit. If the backend - # cannot satisfy the tools the prompt is built around, fall back to the base - # strategy so the dependent prompt is never rendered. `None` = no gating. - if available_tools is not None: - missing = strategy.required_tools() - available_tools - if missing: - logger.warning( - f"Strategy {type(strategy).__name__} requires unavailable tool(s)" - f" {sorted(missing)}; falling back to base FailureAnalysisStrategy." - ) - return FailureAnalysisStrategy() - return strategy - - def _get_active_subgoal_hash(self) -> str: - if not self.ctx.data_engine: - return "default" - task_plan_path = get_note_file_path(self.ctx.data_engine.base_dir, "task_plan") - if not task_plan_path.exists(): - return "default" - try: - content = task_plan_path.read_text(encoding="utf-8") - parent_hash, _ = get_active_subgoal_hashes(content) - return parent_hash - except Exception as e: - logger.error(f"Failed to parse active subgoal: {e}") - return "default" - - def _build_plan_and_history(self, steps: list[dict], task_plan: str) -> str: - active_subgoal_hash = self._get_active_subgoal_hash() - return build_history_for( - "failure_analyzer", - task_plan, - steps, - active_subgoal_hash, - ) - - def _prune_intermediate_screenshots(self, messages: list[Any]): - """Prunes binary screenshot blocks from intermediate messages.""" - prune_intermediate_screenshots(messages) - - @trace(type="agent", name="failure_analyzer") - async def analyze( - self, - state: State, - failed_action: dict, - error_msg: str, - pre_screenshot: str | None = None, - post_screenshot: str | None = None, - pre_screenshot_name: str | None = None, - post_screenshot_name: str | None = None, - executed_actions: list[dict] | None = None, - unexecuted_actions: list[dict] | None = None, - error_category: ValidationErrorCategory = ValidationErrorCategory.GENERAL, - ) -> dict: - if not post_screenshot or post_screenshot == "None": - try: - controller = UnifiedMobileController(self.ctx) - post_screenshot = await controller.take_screenshot() - except Exception as e: - logger.warning(f"Failed to capture post-action fallback screenshot: {e}") - - if not pre_screenshot or pre_screenshot == "None": - pre_screenshot = post_screenshot - - if not pre_screenshot: - raise ValueError("Failure analysis requires valid screenshots.") - - controller = UnifiedMobileController(self.ctx) - executor = McpActionExecutor(self.ctx, controller) - - strategy = self._select_strategy( - error_category, available_tools=frozenset(executor.action_tool_names) - ) - prompt_template_name = strategy.get_prompt_template_name() - tools_declaration = strategy.get_tools() - - try: - llm = get_llm(self.ctx, name="validator_failure_analyzer") - except Exception: - llm = get_llm(self.ctx, name="validator") - - # Pre-Action and Post-Action XML formatting - pre_xml_list_str = "Not available." - if hasattr(state, "latest_ui_hierarchy") and state.latest_ui_hierarchy: - try: - width = getattr(self.ctx.device, "device_width", 1080) - height = getattr(self.ctx.device, "device_height", 2400) - pre_xml_list_str, _, _ = format_minimal_list_with_elements( - state.latest_ui_hierarchy, width, height - ) - except Exception as e: - logger.warning(f"Failed to format pre-action XML list: {e}") - - post_xml_list_str = "Not available." - try: - xml_hierarchy = await controller.get_ui_elements() - width = getattr(self.ctx.device, "device_width", 1080) - height = getattr(self.ctx.device, "device_height", 2400) - ocr_results = [] - - fused_xml = fuse_ocr_with_xml(xml_hierarchy, ocr_results) - - post_xml_list_str, elements, _ = format_minimal_list_with_elements( - fused_xml, width, height - ) - state.indexed_points = [el["center"] for el in elements] - state.indexed_elements = elements - except Exception as e: - logger.warning(f"Failed to fetch/format post-action XML list: {e}") - - prompt_path = Path(__file__).parent.joinpath(prompt_template_name) - prompt_template = prompt_path.read_text(encoding="utf-8") - - steps = [] - task_plan = "No task plan yet." - if self.ctx.data_engine: - steps = self.ctx.data_engine.get_agent_friendly_steps() - task_plan_path = get_note_file_path(self.ctx.data_engine.base_dir, "task_plan") - if task_plan_path.exists(): - try: - task_plan = task_plan_path.read_text(encoding="utf-8") - except Exception as e: - task_plan = f"Error reading task plan: {e}" - - plan_and_history = self._build_plan_and_history(steps, task_plan) - failed_step_number = steps[-1]["step_number"] if steps else 1 - failed_action_description = ( - format_action_clean(failed_action) if failed_action else "Planned Action" - ) - - system_prompt = Template(prompt_template).render( - initial_goal=getattr(state, "initial_goal", "Unknown Goal"), - plan_and_history=plan_and_history, - failed_step_number=failed_step_number, - failed_action_description=failed_action_description, - ) - - suffix = strategy.get_user_message_suffix(bool(pre_screenshot), bool(post_screenshot)) - user_msg = ( - suffix.strip() - if suffix - else "Please analyze and repair the failure shown in the last step of the history." - ) - is_pixel_strategy = isinstance(strategy, PixelTargetDisappearedStrategy) - - user_content: list[dict] = [{"type": "text", "text": user_msg}] - if pre_screenshot: - user_content.append( - {"type": "text", "text": "--- Screenshot Seen During System Decision ---"} - ) - user_content.append( - { - "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{pre_screenshot}"}, - } - ) - if not is_pixel_strategy: - user_content.append( - { - "type": "text", - "text": ( - "--- UI Element List Seen During System Decision ---\n" - f"{pre_xml_list_str}" - ), - } - ) - if post_screenshot: - user_content.append( - {"type": "text", "text": "--- Latest Screenshot (Failed State) ---"} - ) - user_content.append( - { - "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{post_screenshot}"}, - } - ) - if not is_pixel_strategy: - user_content.append( - { - "type": "text", - "text": ( - f"--- Latest UI Element List (Failed State) ---\n{post_xml_list_str}" - ), - } - ) - if suffix: - user_content.append( - {"type": "text", "text": f"\n\n--- INSTRUCTIONS ---\n{suffix.strip()}"} - ) - - messages: list[BaseMessage] = [ - SystemMessage(content=system_prompt), - HumanMessage(content=user_content), - ] - - max_iterations = getattr(self, "max_iterations", 15) - iterations = 0 - final_report = None - intermediate_thoughts = [] - - while iterations < max_iterations: - iterations += 1 - self._prune_intermediate_screenshots(messages) - logger.info(f"Iteration {iterations}: Invoking Universal LLM for FailureAnalyzer...") - - if iterations == max_iterations: - messages.append( - HumanMessage( - content=( - "[WARNING] This is your final iteration; only" - " 'report_failure_analysis' is available." - ) - ) - ) - current_tools = [ - t for t in tools_declaration if t.name == "report_failure_analysis" - ] - else: - current_tools = tools_declaration - - bound_llm = llm.bind_tools(current_tools) - - max_retries = 3 - response = None - for attempt in range(max_retries): - try: - response = await bound_llm.ainvoke(messages) - break - except Exception as e: - logger.warning(f"Failure analyzer turn failed on attempt {attempt + 1}: {e}") - if attempt == max_retries - 1: - raise e - await asyncio.sleep(1.0 * (2**attempt)) - - if response is None: - break - - messages.append(response) - - raw_text = response.content if isinstance(response.content, str) else "" - if isinstance(response.content, list): - raw_text = "".join( - b.get("text", "") - for b in response.content - if isinstance(b, dict) and "text" in b - ) - if raw_text: - intermediate_thoughts.append(raw_text) - - tool_calls = response.tool_calls or [] - if not tool_calls: - messages.append( - HumanMessage( - content=( - "CRITICAL: You did not call the `report_failure_analysis` tool." - " You MUST call an action tool to repair or `report_failure_analysis`" - " to finish." - ) - ) - ) - continue - - submit_call = next( - (tc for tc in tool_calls if tc["name"].split(":")[-1] == "report_failure_analysis"), - None, - ) - - if submit_call: - args = submit_call.get("args") or {} - actions = args.get("new_remaining_actions", []) - final_report = { - "status": args.get("status", "cannot_fix"), - "analysis": args.get("analysis", ""), - "new_remaining_actions": actions, - "thoughts": intermediate_thoughts, - } - messages.append( - ToolMessage( - tool_call_id=submit_call.get("id") or str(uuid.uuid4()), - name=submit_call["name"], - content=json.dumps({"status": "acknowledged"}), - status="success", - ) - ) - break - - # Execute other tool calls via MobileActionExecutor - for tc in tool_calls: - name = tc["name"].split(":")[-1] if ":" in tc["name"] else tc["name"] - args = tc.get("args") or {} - tc_id = tc.get("id") or str(uuid.uuid4()) - logger.info(f"Failure analyzer executing tool '{name}'...") - - try: - exec_result = await executor.execute(name, args, tc_id, state) - messages.append(exec_result.to_langchain_tool_message()) - except Exception as e: - logger.error(f"Error executing tool {name}: {e}") - messages.append( - ToolMessage( - tool_call_id=tc_id, - name=name, - content=f"Error executing tool {name}: {e}", - status="error", - ) - ) - - return final_report or { - "status": "cannot_fix", - "analysis": "Max iterations reached without resolution.", - } diff --git a/artemis/agents/validator/incidents.py b/artemis/agents/validator/incidents.py new file mode 100644 index 0000000..0231d8b --- /dev/null +++ b/artemis/agents/validator/incidents.py @@ -0,0 +1,120 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Execution failures passed from the Validator to the Operator. + +Incidents are stored in ``state.open_incident`` and the step's +``last_execution_result``. Consecutive failures increment the count; a +successful terminal action closes the incident. Prompt rendering lives in +``artemis.agents.operator.prompts.ExecutionIncidentPromptComponent``. +""" + +from dataclasses import asdict, dataclass, field +import time +from typing import Any + +from artemis.agents.validator.categories import ValidationErrorCategory + +#: The safety net refused to execute the action (target missing/shifted/covered). +KIND_SAFETY_NET = "safety_net" +#: The action was dispatched but the device/executor reported a failure. +KIND_EXEC_ERROR = "exec_error" + +#: Keys of an action item that are bulky or internal and never useful to the +#: Operator when the incident is rendered or persisted. +_ACTION_ITEM_DROP_KEYS = frozenset( + { + "attempts", + "safety_net_evidence", + "thought", + } +) + + +@dataclass +class ExecutionIncident: + """One blocked turn-ending action, as reported to the Operator.""" + + kind: str + category: str + reason: str + action: dict[str, Any] + action_description: str + action_index: int = 0 + burst_size: int = 1 + step_number: int | None = None + consecutive_failures: int = 1 + evidence: dict[str, Any] = field(default_factory=dict) + opened_at: float = field(default_factory=time.time) + + @property + def is_burst(self) -> bool: + return self.burst_size > 1 + + def to_dict(self) -> dict[str, Any]: + return asdict(self) + + @classmethod + def from_dict(cls, data: dict[str, Any] | None) -> "ExecutionIncident | None": + if not isinstance(data, dict) or not data.get("kind"): + return None + known = {f for f in cls.__dataclass_fields__} + # Older step ledgers may lack these fields. + kwargs: dict[str, Any] = { + "category": str(ValidationErrorCategory.GENERAL), + "reason": "", + "action": {}, + "action_description": "", + } + kwargs.update({k: v for k, v in data.items() if k in known}) + return cls(**kwargs) + + +def sanitize_action_item(action_item: dict[str, Any]) -> dict[str, Any]: + """A copy of the action item without bulky/internal keys.""" + return {k: v for k, v in (action_item or {}).items() if k not in _ACTION_ITEM_DROP_KEYS} + + +def open_incident( + *, + previous: dict[str, Any] | None, + kind: str, + category: ValidationErrorCategory | str, + reason: str, + action_item: dict[str, Any], + action_description: str, + action_index: int = 0, + burst_size: int = 1, + step_number: int | None = None, + evidence: dict[str, Any] | None = None, +) -> dict[str, Any]: + """Builds the incident dict for a failed action. + + Continue the failure count from ``previous`` if an incident is still open. + """ + prior = ExecutionIncident.from_dict(previous) + consecutive = (prior.consecutive_failures + 1) if prior else 1 + incident = ExecutionIncident( + kind=kind, + category=str(category.value if isinstance(category, ValidationErrorCategory) else category), + reason=(reason or "").strip(), + action=sanitize_action_item(action_item), + action_description=action_description, + action_index=action_index, + burst_size=burst_size, + step_number=step_number, + consecutive_failures=consecutive, + evidence=dict(evidence or {}), + ) + return incident.to_dict() diff --git a/artemis/agents/validator/pixel_safety_net.md b/artemis/agents/validator/pixel_safety_net.md index d796072..2156426 100644 --- a/artemis/agents/validator/pixel_safety_net.md +++ b/artemis/agents/validator/pixel_safety_net.md @@ -4,7 +4,7 @@ UI State Verification Expert. Compare Image 1 (Reference) and Image 2 (Current S Rules 1. Check Intent: Read the `[Planned Action & Original Thinking]` to identify what Action the Agent intended to perform and why. 2. Specific UI Elements (Priority): If targeting a specific UI control (e.g., button, icon, text, slider handle) — even if it overlays a dynamic video, map, or live stream — focus ONLY on that element's shape. Output `{"is_present": false, ...}` if that exact element has disappeared, shifted away, or is blocked. Ignore background media changes underneath. -3. Blank Space & Backgrounds: If targeting open/blank space (e.g., to wake up hidden controls or dismiss dialogs), ignore natural video/background frame changes. Output `{"is_present": true, "confidence": 1.0}` unless unexpected blocking popups appear. +3. Blank Space, Backgrounds & Media Surfaces: If targeting open/blank space or the body of a playing video / animation / map / live stream itself rather than a specific control drawn on top of it (e.g., tapping a full-screen video to wake its hidden controls, tapping the backdrop to dismiss a dialog), the frames are expected to differ: ignore natural video/background frame changes and the appearance or fading of overlay controls. Output `{"is_present": true, "confidence": 1.0}` unless an unexpected blocking popup now covers the tap point. Output Return EXACTLY this raw JSON and nothing else. Keep reasoning strictly under 25 words. diff --git a/artemis/agents/validator/pixel_target_disappeared_analyzer.md b/artemis/agents/validator/pixel_target_disappeared_analyzer.md deleted file mode 100644 index f446777..0000000 --- a/artemis/agents/validator/pixel_target_disappeared_analyzer.md +++ /dev/null @@ -1,65 +0,0 @@ -# Identity & Goal -You are a specialized error recovery agent. You will be provided with a failed action. Your ONLY task is to repair this specific failure and restore the system state so the main system can resume. - - -# Latency Awareness Principle -You operate in an environment with unavoidable "Turn Latency" (a delay of several seconds between screen capture, decision making, and tool execution). -Consequently, any transient UI element (e.g., auto-dismissing overlays, temporary message bars, menus with short timeouts) WILL disappear before your next decision turn if you attempt to interact with them step-by-step. - -# Context -- **Initial Goal**: {{ initial_goal }} -- **History & Plan**: -{{ plan_and_history }} - -# Core Workflow -1. **Analyze**: Compare the two states provided to you: - - **Decision State (`Screenshot Seen During System Decision`)**: This is the screen the system saw when it originally planned the action. It shows the target element successfully located. - - **Failed State (`Latest Screenshot (Failed State)`)**: This is the live screen captured at the moment you were summoned. It shows the current state where the target is missing or unresponsive. - Your goal in this comparison is to: - - Read the Operator's thought process in the decision loop of the failed step to understand what they were originally trying to accomplish with this failed action. - - Understand the expected target layout. - - Identify the specific visual pattern (e.g., a play triangle, a close 'X', an arrow icon) at the failed target area from the reference state. - - Identify why this target element visually disappeared or is no longer responsive (use the **Diagnosis Tips** below). -2. **Recover & Execute**: Restore the target state and execute the failed action. - - **Flexibility**: Focus on outcome over form. Take alternative/adaptive steps (e.g., scroll, navigate, different click paths) if original target positions are invalid. - - **Transient State Handling**: If the failed target is part of a transient state, you MUST NOT execute the recovery and target interaction in separate turns. - - **Atomic Chained Execution**: Combine the summon action (on the Trigger Element) and the target action (on the Transient Target) into a single atomic sequence. Use `click_sequence(sequence=[Trigger, Target])` to execute them with a 50ms delay, bypassing the Turn Latency gap. -3. **Report**: Call `report_failure_analysis` to conclude: - - `status="fixed"`: Successfully executed the failed action or reached the destination state. - - `status="cannot_fix"`: Target is unrecoverable, the app is broken, or the 15-step budget is exceeded. - - `analysis`: Summarize visual mismatches and actions taken. - -# Special Tools -- **`click_sequence(sequence)`**: Designed to defeat Turn Latency. Chain your click sequence to handle transient elements. -- **`ask_explorer(query, context_feedback)`**: Call this visual parsing subagent to search for the visual icon/pattern on the current screen (e.g., `query="play icon"`) if you suspect the target has shifted, or coordinates are ambiguous. - -# Diagnosis Tips -* **Time-Sensitive States (MOST COMMON & PRIMARY CAUSE)**: - - *Cue*: The target element was visible when the system made its decision, but has disappeared in the failed state because it belongs to an auto-closing, short-timeout, or transient UI state. - - *Action*: Re-trigger the state dynamically from the current screen and chain it with the failed step in a rapid consecutive sequence. - - **Mental Model**: Tap the persistent summoner element (Trigger) that wakes up the transient UI + Tap the target coordinates (Failed Target) before it has time to auto-close, combining both into a single atomic movement. - - **Procedure**: - 1. **Locate the Trigger (Target 1)**: Inspect the current screen to identify a persistent parent element or background area (e.g., the container element or parent dropdown header) that, when tapped, forces the transient UI state to reappear. - 2. **Retrieve the Failed Target (Target 2)**: Look up the coordinates of the FAILED action from the failed step in the history (since the target button is currently invisible on your screen). - 3. **Chained Execution**: Call `click_sequence(sequence=[Target 1, Target 2])` to execute both taps consecutively with a 50ms delay, catching the transient element before the timeout closes it. *(Note: This sequence fulfills your workflow requirement to "execute the failed action".)* -* **False Alarm**: - - *Cue*: Target element is actually present on the current screen (minor validation flake). - - *Action*: Directly report it as `"fixed"` and hand control back to the system. -* **Visual Target Shifts**: - - *Cue*: The target visual icon has shifted due to screen layout changes, orientations, or dynamic list updates. - - *Action*: Visually locate the icon on the live screenshot and click its new coordinates. You may call `ask_explorer(query="describe the visual icon shape or labels")` to dynamically locate it and obtain the new coordinates if they are hard to estimate manually. -* **Overlays**: - - *Cue*: Unexpected permission prompts, dialogs, or system popups block the screen. - - *Action*: Tap outside or click the dismiss/close buttons to clear the overlay. -* **Keyboard & Scroll Positioning**: - - *Cue*: Target is scrolled off-screen or blocked by the on-screen keyboard. - - *Action*: Scroll to reposition the target, or hide the keyboard. -* **Navigation Drift**: - - *Cue*: App unexpectedly transitioned to a different tab or screen. - - *Action*: Navigate back to the correct target screen. -* **App Instability**: - - *Cue*: Application crashed, froze, or became unresponsive. - - *Action*: Force stop and relaunch the app. - -# Constraints -The most recent step (Step {{ failed_step_number }}: {{ failed_action_description }}) failed to execute. You must resolve the issue and report the results in 15 interactive steps or fewer. Now please begin the repair analysis. Please note that during the **Recover & Execute** phase, you only need to resolve the currently failed step; do not attempt to execute any subsequent steps or complete the overall goal yourself. Once the failure is resolved, please immediately enter the **Report** phase and return control to the system. diff --git a/artemis/agents/validator/precondition_pixel.py b/artemis/agents/validator/precondition_pixel.py index 422bb60..14d9806 100644 --- a/artemis/agents/validator/precondition_pixel.py +++ b/artemis/agents/validator/precondition_pixel.py @@ -30,7 +30,7 @@ from pathlib import Path from langchain_core.messages import HumanMessage, SystemMessage -from artemis.agents.validator.failure_analyzer import ValidationErrorCategory +from artemis.agents.validator.categories import ValidationErrorCategory from artemis.context import ArtemisContext from artemis.graph.state import State from artemis.services.llm import acomplete_structured @@ -191,8 +191,7 @@ async def _run_attempt( ) logger.warning( - "Pixel validation failed but confidence" - f" ({confidence:.2f}) is below 0.7. Bypassing check." + f"Pixel validation failed but confidence ({confidence:.2f}) is below 0.7. Bypassing check." ) return True, ValidationErrorCategory.PIXEL_BYPASSED, "" @@ -237,18 +236,14 @@ async def validate_action_precondition_pixel( orig_bytes, orig_coords, crop_size=None, dot_radius=15 ) except Exception as e: - logger.error( - f"Failed to prepare original crop for pixel safety net: {e}. Bypassing check." - ) + logger.error(f"Failed to prepare original crop for pixel safety net: {e}. Bypassing check.") return True, ValidationErrorCategory.PIXEL_BYPASSED, "" # 3. Get LLM Configuration once outside the loop try: llm, prompt = _init_llm_and_prompt(ctx, get_llm_fn) except Exception as e: - logger.error( - f"Failed to initialize LLM/prompt for pixel safety net: {e}. Bypassing check." - ) + logger.error(f"Failed to initialize LLM/prompt for pixel safety net: {e}. Bypassing check.") return True, ValidationErrorCategory.PIXEL_BYPASSED, "" # 4. Start high-precision execution validation retry loop diff --git a/artemis/agents/validator/precondition_xml.py b/artemis/agents/validator/precondition_xml.py index 501cf93..52f5bdc 100644 --- a/artemis/agents/validator/precondition_xml.py +++ b/artemis/agents/validator/precondition_xml.py @@ -17,7 +17,7 @@ Checks that the element the Operator decided to interact with is still present on the live screen (via the UI hierarchy) and substantially consistent, with coordinate self-healing for small drifts and a structured failure taxonomy -(shifted / occupied / disappeared) consumed by the FailureAnalyzer. +(shifted / occupied / disappeared) surfaced to the Operator as an execution incident. Extracted from ``validator.py``; the public entry points remain the ``ValidatorNode`` methods, which delegate here. @@ -28,7 +28,7 @@ import difflib import math import re -from artemis.agents.validator.failure_analyzer import ValidationErrorCategory +from artemis.agents.validator.categories import ValidationErrorCategory from artemis.constants import VALIDATOR_UI_HIERARCHY_TIMEOUT from artemis.context import ArtemisContext from artemis.graph.state import State @@ -125,8 +125,7 @@ async def _fetch_live_elements(session): elements = await session.ui_hierarchy(timeout=VALIDATOR_UI_HIERARCHY_TIMEOUT) except Exception as e: logger.warning( - f"Failed to get live XML via MCP: {e!r}. Falling back to" - " Pixel-based validation." + f"Failed to get live XML via MCP: {e!r}. Falling back to Pixel-based validation." ) return None, ( False, @@ -146,8 +145,7 @@ async def _fetch_live_elements(session): except Exception as e: logger.error( - f"Unexpected error during live XML fetch: {e}. Falling back to" - " Pixel-based validation." + f"Unexpected error during live XML fetch: {e}. Falling back to Pixel-based validation." ) return None, ( False, @@ -278,9 +276,7 @@ def _combine_signals( signals.append(W_COORD * (1.0 if contains_coord else 0.0)) score = sum(signals) / sum(weights) if weights else 1.0 - identity_score = ( - sum(identity_signals) / sum(identity_weights) if identity_weights else 1.0 - ) + identity_score = sum(identity_signals) / sum(identity_weights) if identity_weights else 1.0 return score, identity_score @@ -554,6 +550,11 @@ def _classify_failure( f" has shifted.\n- New location: {best['center']} (bounds:" f" {best['bounds']})" ) + # Structured facts for the execution incident handed to the Operator. + action_item["safety_net_evidence"] = { + "new_center": list(best["center"]), + "new_bounds": best.get("bounds"), + } return False, ValidationErrorCategory.TARGET_SHIFTED, reason # 2. Occupied Case @@ -568,6 +569,10 @@ def _classify_failure( " occupied/intercepted by a different element:" f" {occupant_desc} at bounds {occupant_bounds}." ) + action_item["safety_net_evidence"] = { + "occupant": occupant_desc, + "occupant_bounds": occupant_bounds, + } return False, ValidationErrorCategory.TARGET_OCCUPIED, reason # 3. Completely Missing Case diff --git a/artemis/agents/validator/target_disappeared_analyzer.md b/artemis/agents/validator/target_disappeared_analyzer.md deleted file mode 100644 index 8bcc61b..0000000 --- a/artemis/agents/validator/target_disappeared_analyzer.md +++ /dev/null @@ -1,61 +0,0 @@ -# Identity & Goal -You are a specialized error recovery agent. You will be provided with a failed action. Your ONLY task is to repair this specific failure and restore the system state so the main system can resume. - - -# Latency Awareness Principle -You operate in an environment with unavoidable "Turn Latency" (a delay of several seconds between screen capture, decision making, and tool execution). -Consequently, any transient UI element (e.g., auto-dismissing overlays, temporary message bars, menus with short timeouts) WILL disappear before your next decision turn if you attempt to interact with them step-by-step. - -# Context -- **Initial Goal**: {{ initial_goal }} -- **History & Plan**: -{{ plan_and_history }} - -# Core Workflow -1. **Analyze**: Compare the two states provided to you: - - **Decision State (`Screenshot Seen During System Decision`)**: This is the screen the system saw when it originally planned the action. It shows the target element successfully located. - - **Failed State (`Latest Screenshot (Failed State)`)**: This is the live screen captured at the moment you were summoned. It shows the current state where the target is missing or unresponsive. - Your goal in this comparison is to: - - Read the Operator's thought process in the decision loop of the failed step to understand what they were originally trying to accomplish with this failed action. - - Understand the expected target layout. - - Identify why the target element disappeared (use the **Diagnosis Tips** below). -2. **Recover & Execute**: Restore the target state and execute the failed action. - - **Flexibility**: Focus on outcome over form. Take alternative/adaptive steps (e.g., scroll, navigate, different click paths) if original coordinates are invalid. - - **Transient State Handling**: If the failed target is part of a transient state, you MUST NOT execute the recovery and target interaction in separate turns. - - **Atomic Chained Execution**: Combine the summon action (on the Trigger Element) and the target action (on the Transient Target) into a single atomic sequence. Use `click_sequence(sequence=[Trigger, Target])` to execute them with a 50ms delay, bypassing the Turn Latency gap. -3. **Report**: Call `report_failure_analysis` to conclude: - - `status="fixed"`: Successfully executed the failed action or reached the destination state. - - `status="cannot_fix"`: Target is unrecoverable, the app is broken, or the 15-step budget is exceeded. - - `analysis`: Summarize visual mismatches and actions taken. - -# Special Tools -- **`click_sequence(sequence)`**: Designed to defeat Turn Latency. Chain your click sequence to handle transient elements. -- **`ask_explorer(query, context_feedback)`**: Call this visual parsing subagent to search for the visual icon/pattern on the current screen if the target is absent from the standard "UI Element List" but you see it is visually present on the screenshot. - -# Diagnosis Tips -* **Time-Sensitive States (MOST COMMON & PRIMARY CAUSE)**: - - *Cue*: The target element was visible when the system made its decision, but has disappeared in the failed state because it belongs to an auto-closing, short-timeout, or transient UI state. - - *Action*: Re-trigger the state dynamically from the current screen and chain it with the failed step in a rapid consecutive sequence. - - **Mental Model**: Tap the persistent summoner element (Trigger) that wakes up the transient UI + Tap the target coordinates (Failed Target) before it has time to auto-close, combining both into a single atomic movement. - - **Procedure**: - 1. **Locate the Trigger (Target 1)**: Inspect the current screen to identify a persistent parent element or background area (e.g., the container element or parent dropdown header) that, when tapped, forces the transient UI state to reappear. - 2. **Retrieve the Failed Target (Target 2)**: Look up the coordinates of the FAILED action from the failed step in the history (since the target button is currently invisible on your screen). - 3. **Chained Execution**: Call `click_sequence(sequence=[Target 1, Target 2])` to execute both taps consecutively with a 50ms delay, catching the transient element before the timeout closes it. *(Note: This sequence fulfills your workflow requirement to "execute the failed action".)* -* **False Alarm**: - - *Cue*: Target element is actually present on the current screen (minor validation flake). - - *Action*: Directly report it as `"fixed"` and hand control back to the system. -* **Overlays**: - - *Cue*: Unexpected permission prompts, dialogs, or system popups block the screen. - - *Action*: Tap outside or click the dismiss/close buttons to clear the overlay. -* **Keyboard & Scroll Positioning**: - - *Cue*: Target is scrolled off-screen or blocked by the on-screen keyboard. - - *Action*: Scroll to reposition the target, or hide the keyboard. -* **Navigation Drift**: - - *Cue*: App unexpectedly transitioned to a different tab or screen. - - *Action*: Navigate back to the correct target screen. -* **App Instability**: - - *Cue*: Application crashed, froze, or became unresponsive. - - *Action*: Force stop and relaunch the app. - -# Constraints -The most recent step (Step {{ failed_step_number }}: {{ failed_action_description }}) failed to execute. You must resolve the issue and report the results in 15 interactive steps or fewer. Now please begin the repair analysis. Please note that during the **Recover & Execute** phase, you only need to resolve the currently failed step; do not attempt to execute any subsequent steps or complete the overall goal yourself. Once the failure is resolved, please immediately enter the **Report** phase and return control to the system. diff --git a/artemis/agents/validator/tool_declarations.py b/artemis/agents/validator/tool_declarations.py index 4fca084..1c24f03 100644 --- a/artemis/agents/validator/tool_declarations.py +++ b/artemis/agents/validator/tool_declarations.py @@ -25,7 +25,12 @@ from google.genai import types as genai_types from langchain_core.messages import ToolMessage from pydantic import BaseModel, Field -from artemis.config import resolve_explorer_version +from artemis.agents.explorer.constants import ( + ASK_EXPLORER_CONTEXT_FEEDBACK_DESCRIPTION, + ASK_EXPLORER_DESCRIPTION, + ASK_EXPLORER_QUERY_DESCRIPTION, + ASK_EXPLORER_TOOL_NAME, +) from artemis.context import ArtemisContext from artemis.controllers.unified_controller import UnifiedMobileController from artemis.core.tool_declaration import ToolDeclaration @@ -34,7 +39,7 @@ from artemis.mcp.action_specs import tool_declaration from artemis.mcp.action_types import ActionCode from artemis.mcp.actuators.adb import AdbActuator from artemis.mcp.observation import observe -from artemis.tools.explorer_tool import _run_explorer_logic +from artemis.tools.explorer_tool import locate, register_candidates, render_text from artemis.graph.state import State from artemis.utils.coordinates import ( compute_smart_swipe_coordinates, @@ -143,8 +148,13 @@ def prune_intermediate_screenshots(messages: list[Any]) -> None: ) ) ) - except Exception: - pass + except Exception as exc: + logger.debug( + "Could not build placeholder text part for pruned screenshot;" + " dropping it: %s", + exc, + exc_info=True, + ) else: new_parts.append(part) content.parts = new_parts @@ -264,6 +274,14 @@ def serialize_mobile_action_result(result: Any) -> dict[str, Any]: } +def serialize_explorer_result(result: Any) -> dict[str, Any]: + """Stores an ``(text, ok)`` Explorer answer as a flat trace payload.""" + if isinstance(result, tuple) and len(result) == 2: + text, ok = result + return {"outcome": text, "status": "success" if ok else "error"} + return {"outcome": str(result) if result is not None else ""} + + class MobileActionExecutor: """Adapts LLM tool calls onto an actuator backend and captures observations. @@ -278,10 +296,14 @@ class MobileActionExecutor: ctx: ArtemisContext, controller: UnifiedMobileController | None = None, actuator: "AdbActuator | None" = None, + agent_name: str = "validator", ): self.ctx = ctx self.actuator = actuator or AdbActuator(ctx, controller) self.controller = self.actuator.controller + # Identifies the calling agent to the Explorer tier resolver: the tier + # is a user setting keyed by profile / agent, never chosen by the agent. + self.agent_name = agent_name @trace(type="action", name="click", serializer=serialize_mobile_action_result) async def exec_click( @@ -327,7 +349,7 @@ class MobileActionExecutor: except Exception: try: sequence = ast.literal_eval(sequence_str) - except Exception: + except (ValueError, TypeError, SyntaxError, MemoryError, RecursionError): pass if not isinstance(sequence, (list, tuple)): @@ -622,37 +644,27 @@ class MobileActionExecutor: except Exception as e: return format_list_notes_failure(str(e)) - @trace(type="tool", name="ask_explorer") + @trace(type="tool", name="ask_explorer", serializer=serialize_explorer_result) async def exec_ask_explorer( self, query: str, context_feedback: str | None, state: State, - version: str | None = None, - ) -> str: + ) -> tuple[str, bool]: + """Runs the Explorer pipeline; returns ``(text, ok)``. + + ``ok`` is False only when the Explorer run itself failed. A clean + "not found" is a successful answer the agent must reason about, so it + must not be reported as a tool error. + """ try: - active_version = resolve_explorer_version( - self.ctx, - explicit_version=version, - agent_or_profile_name="validator", + outcome = await locate( + self.ctx, state, query, context_feedback or "", agent_name=self.agent_name ) - result = await _run_explorer_logic( - self.ctx, - state, - query, - context_feedback or "", - version=active_version, - ) - if isinstance(result, list): - texts = [ - item["text"] - for item in result - if isinstance(item, dict) and item.get("type") == "text" - ] - return "\n".join(texts) if texts else str(result) - return str(result) + registered = register_candidates(self.ctx, state, outcome) + return render_text(query, outcome, registered), not outcome.error except Exception as e: - return f"Error executing ask_explorer: {e}" + return f"Error executing ask_explorer: {e}", False async def execute( self, @@ -665,6 +677,9 @@ class MobileActionExecutor: raw_name = name.split(":")[-1] if ":" in name else name res_text = "" img_bytes, shot_path, xml_list = None, None, None + # Explicit status for tools that report it (the Explorer); None keeps the + # historical text-sniffing fallback for the device actions. + explicit_ok: bool | None = None if raw_name == "click": res_text, img_bytes, shot_path, xml_list = await self.exec_click( @@ -724,7 +739,9 @@ class MobileActionExecutor: elif raw_name == "list_notes": res_text = await self.exec_list_notes() elif raw_name == "ask_explorer": - res_text = await self.exec_ask_explorer( + res_text, explicit_ok = await self.exec_ask_explorer( + # ``task_description`` is the pre-contract argument name kept + # for old MCP clients. args.get("query") or args.get("task_description") or "", args.get("context_feedback"), state, @@ -738,7 +755,9 @@ class MobileActionExecutor: status: Literal["success", "error"] = ( "success" if (img_bytes is not None or raw_name not in ACTION_TOOL_NAMES) else "error" ) - if "Error" in res_text or "Failed" in res_text: + if explicit_ok is not None: + status = "success" if explicit_ok else "error" + elif "Error" in res_text or "Failed" in res_text: status = "error" return ToolExecutionResult( @@ -799,34 +818,6 @@ MANAGE_APP_TOOL = tool_declaration("manage_app") WAIT_FOR_DELAY_TOOL = tool_declaration("wait_for_delay") -REPORT_FAILURE_ANALYSIS_TOOL = ToolDeclaration( - name="report_failure_analysis", - description=( - "[REPORT] Use this tool to return your final answer when you are done" - " with the analysis and optional repair. This is the ONLY way to return" - " your final conclusion." - ), - parameters={ - "type": "object", - "properties": { - "status": { - "type": "string", - "description": ( - "Whether the failure was repaired locally ('fixed') or not ('cannot_fix')." - ), - }, - "analysis": { - "type": "string", - "description": ( - "Provide the cause analysis and handling method description in a single" - " paragraph, and append the specific operations you ran." - ), - }, - }, - "required": ["status", "analysis"], - }, -) - REPORT_TASK_STATUS_TOOL = ToolDeclaration( name="report_task_status", description=( @@ -852,27 +843,24 @@ REPORT_TASK_STATUS_TOOL = ToolDeclaration( }, ) +# Same contract as the Operator's LangChain tool (artemis/tools/explorer_tool.py): +# the Explorer tier is a user setting, so no tier argument is exposed here. ASK_EXPLORER_TOOL = ToolDeclaration( - name="ask_explorer", - description=( - "[EXPLORER] Call the Explorer sub-agent to locate an element or" - " coordinate on the screen when XML hierarchy lacks coordinates or" - " visual target is ambiguous." - ), + name=ASK_EXPLORER_TOOL_NAME, + description=ASK_EXPLORER_DESCRIPTION, parameters={ "type": "object", "properties": { - "task_description": { + "query": { "type": "string", - "description": "The description of what to find on the screen.", + "description": ASK_EXPLORER_QUERY_DESCRIPTION, }, - "roi": { - "type": "array", - "items": {"type": "integer"}, - "description": "Optional bounding box [ymin, xmin, ymax, xmax] in 0-1000 scale.", + "context_feedback": { + "type": "string", + "description": ASK_EXPLORER_CONTEXT_FEEDBACK_DESCRIPTION, }, }, - "required": ["task_description"], + "required": ["query"], }, ) @@ -886,5 +874,4 @@ VALIDATOR_TOOLS_DECLARATION: list[ToolDeclaration] = [ LIST_NOTES_TOOL, MANAGE_APP_TOOL, WAIT_FOR_DELAY_TOOL, - REPORT_FAILURE_ANALYSIS_TOOL, ] diff --git a/artemis/agents/validator/validator.py b/artemis/agents/validator/validator.py index 080cd2c..d7979b7 100644 --- a/artemis/agents/validator/validator.py +++ b/artemis/agents/validator/validator.py @@ -40,10 +40,7 @@ from artemis.agents.validator import ( precondition_pixel, precondition_xml, ) -from artemis.agents.validator.failure_analyzer import ( - FailureAnalyzer, # noqa: F401 (re-exported for backward compatibility) - ValidationErrorCategory, -) +from artemis.agents.validator.categories import ValidationErrorCategory from artemis.constants import ( # noqa: F401 (patchable seams; the UI-change # polling path that consumed the poll constants was removed as dead code, # but the names stay importable/patchable at this module path.) @@ -137,8 +134,13 @@ class ValidatorNode: "traceback": err_stack, }, ) - except Exception: - pass + except Exception as record_err: + logger.debug( + "Could not persist validator error status for step %s: %s", + step_id, + record_err, + exc_info=True, + ) raise e finally: if self.ctx.data_engine: diff --git a/artemis/agents/video_analyzer/audio_native.py b/artemis/agents/video_analyzer/audio_native.py index a830251..b889270 100644 --- a/artemis/agents/video_analyzer/audio_native.py +++ b/artemis/agents/video_analyzer/audio_native.py @@ -83,9 +83,7 @@ async def exec_analyze_audio_only( specific_query, modality="audio", model_name=analyzer.model_name, - source_generation=( - active_session.generation if active_session is not None else None - ), + source_generation=(active_session.generation if active_session is not None else None), ) if claim.state == "cached": result = ( @@ -225,8 +223,7 @@ async def _finish_failed_audio( ): try: logger.warning( - "Native audio analysis failed" - f" [{failure.category.value}]; using universal fallback" + f"Native audio analysis failed [{failure.category.value}]; using universal fallback" ) _va._record_llm_event( "llm_fallback", @@ -311,25 +308,17 @@ def _build_audio_prompt( warnings = analyzer.get_overlapping_warnings(start_time, end_time) warning_block = "" if warnings: - lines = [ - "WARNING: The following queries were already tried in this timeframe:\n" - ] + lines = ["WARNING: The following queries were already tried in this timeframe:\n"] for w in warnings: lines.append(f"Searched for: {w['target']} -> {w['summary']}") warning_block = "\n".join(lines) + "\n\n" duration_secs = getattr(result, "duration_seconds", None) - actual_end = ( - actual_start + duration_secs - if isinstance(duration_secs, (int, float)) - else None - ) + actual_end = actual_start + duration_secs if isinstance(duration_secs, (int, float)) else None end_str = f" to {actual_end:.1f}s" if actual_end is not None else "" warning_val = getattr(result, "warning", None) truncation_note = ( - f" (NOTE: {warning_val})" - if isinstance(warning_val, str) and warning_val - else "" + f" (NOTE: {warning_val})" if isinstance(warning_val, str) and warning_val else "" ) prompt_with_context = ( @@ -377,9 +366,7 @@ async def _run_native_audio_conversation( with TraceSpan(name="upload_audio_to_gemini") as span: file = await analyzer.upload_and_poll_file(media.audio_path) span.result = f"Uploaded {file.name}" - logger.info( - f"Invoking Gemini for audio-only task with model {analyzer.model_name}..." - ) + logger.info(f"Invoking Gemini for audio-only task with model {analyzer.model_name}...") sub_agent_contents = [ types.Content( @@ -439,9 +426,7 @@ async def _run_native_audio_conversation( logger.error(f"Failed to delete cloud file {file.name}: {ce}") -async def _drive_audio_agent_loop( - analyzer, sub_agent_contents: list -) -> tuple[str, str, float]: +async def _drive_audio_agent_loop(analyzer, sub_agent_contents: list) -> tuple[str, str, float]: """Iterates the audio agent conversation until submit_answer is accepted.""" sub_max_iterations = 2 sub_iterations = 0 @@ -475,11 +460,7 @@ async def _drive_audio_agent_loop( contents=sub_agent_contents, config=types.GenerateContentConfig( system_instruction=analyzer.audio_system_prompt, - tools=[ - types.Tool( - function_declarations=[analyzer.submit_answer_tool] - ) - ], + tools=[types.Tool(function_declarations=[analyzer.submit_answer_tool])], safety_settings=SAFETY_SETTINGS_BLOCK_NONE, ), ), @@ -528,9 +509,7 @@ def _handle_audio_function_calls( """Processes audio-agent function calls; returns (answered, summary, analysis, score).""" answered = False for fc in function_calls: - if ( - fc.name.split(":")[-1] if ":" in fc.name else fc.name - ) == "submit_answer": + if (fc.name.split(":")[-1] if ":" in fc.name else fc.name) == "submit_answer": args = fc.args if ( "confidence_score" not in args @@ -567,15 +546,9 @@ def _handle_audio_function_calls( answered = "error" break - final_summary = ( - args.get("summary", "No summary provided.") - .strip() - .replace("\n", " ") - ) + final_summary = args.get("summary", "No summary provided.").strip().replace("\n", " ") final_analysis = ( - args.get("analysis", "No analysis provided.") - .strip() - .replace("\n", " ") + args.get("analysis", "No analysis provided.").strip().replace("\n", " ") ) final_confidence_score = float(args["confidence_score"]) answered = True @@ -596,13 +569,7 @@ def _handle_audio_function_calls( parts=[ types.Part.from_function_response( name=function_calls[0].name, - response={ - "error": ( - "Tool not recognized." - " Please use" - " submit_answer." - ) - }, + response={"error": ("Tool not recognized. Please use submit_answer.")}, ) ], ) diff --git a/artemis/agents/video_analyzer/chunk_conversation.py b/artemis/agents/video_analyzer/chunk_conversation.py index d6585b9..76aa014 100644 --- a/artemis/agents/video_analyzer/chunk_conversation.py +++ b/artemis/agents/video_analyzer/chunk_conversation.py @@ -64,8 +64,7 @@ async def _run_native_chunk_conversation( file = await analyzer.upload_and_poll_file(media.compressed_path) span.result = f"Uploaded {file.name}" logger.info( - "Invoking Gemini for sub-agent task with model" - f" {analyzer.model_name} (streaming)..." + f"Invoking Gemini for sub-agent task with model {analyzer.model_name} (streaming)..." ) trace_id = CURRENT_TRACE_ID.get() @@ -209,11 +208,7 @@ async def _stream_sub_agent_turn( contents=sub_agent_contents, config=types.GenerateContentConfig( system_instruction=analyzer.sub_system_prompt, - tools=[ - types.Tool( - function_declarations=[analyzer.submit_answer_tool] - ) - ], + tools=[types.Tool(function_declarations=[analyzer.submit_answer_tool])], safety_settings=SAFETY_SETTINGS_BLOCK_NONE, ), ), @@ -256,18 +251,12 @@ async def _read_sub_agent_stream(analyzer, span, target_stream, current_trace_id if chunk_text: text += chunk_text if analyzer.ctx.data_engine and current_trace_id: - analyzer.ctx.data_engine.stream_output( - current_trace_id, chunk_text - ) + analyzer.ctx.data_engine.stream_output(current_trace_id, chunk_text) if chunk.function_calls: function_calls.extend(chunk.function_calls) candidates = getattr(chunk, "candidates", None) - if ( - candidates - and isinstance(candidates, list) - and len(candidates) > 0 - ): + if candidates and isinstance(candidates, list) and len(candidates) > 0: content = getattr(candidates[0], "content", None) parts = getattr(content, "parts", None) if content else None if parts: @@ -304,20 +293,12 @@ async def _handle_sub_agent_calls( """Processes function calls from one turn; returns (answered, summary, analysis).""" answered = False for fc in function_calls: - if ( - fc.name.split(":")[-1] if ":" in fc.name else fc.name - ) == "submit_answer": + if (fc.name.split(":")[-1] if ":" in fc.name else fc.name) == "submit_answer": args = fc.args timeline_events = args.get("timeline_events", []) - final_summary = ( - args.get("summary", "No summary provided.") - .strip() - .replace("\n", " ") - ) + final_summary = args.get("summary", "No summary provided.").strip().replace("\n", " ") final_analysis = ( - args.get("analysis", "No analysis provided.") - .strip() - .replace("\n", " ") + args.get("analysis", "No analysis provided.").strip().replace("\n", " ") ) error_msg = _validate_timeline_events(timeline_events, start_time, end_time) @@ -349,11 +330,7 @@ async def _handle_sub_agent_calls( function_calls, full_text, function_calls[0].name, - ( - "Tool not recognized." - " Please use" - " submit_answer." - ), + ("Tool not recognized. Please use submit_answer."), ) return answered, final_summary, final_analysis @@ -388,9 +365,7 @@ def _validate_timeline_events( event["verification_timestamp_secs"], (int, float), ) - or isinstance( - event["verification_timestamp_secs"], bool - ) + or isinstance(event["verification_timestamp_secs"], bool) ): return ( "Error:" @@ -409,9 +384,7 @@ def _validate_timeline_events( ) confidence = float(event.get("confidence_score", 0.0)) # noqa: F841 - if verification_ts < start_time or ( - end_time is not None and verification_ts > end_time - ): + if verification_ts < start_time or (end_time is not None and verification_ts > end_time): return ( "Error:" " 'verification_timestamp_secs'" @@ -439,15 +412,8 @@ def _append_rejection( role="model", parts=original_parts if original_parts - else [ - types.Part(function_call=fc) - for fc in function_calls - ] - + ( - [types.Part.from_text(text=full_text)] - if full_text - else [] - ), + else [types.Part(function_call=fc) for fc in function_calls] + + ([types.Part.from_text(text=full_text)] if full_text else []), ) ) sub_agent_contents.append( diff --git a/artemis/agents/video_analyzer/chunk_native.py b/artemis/agents/video_analyzer/chunk_native.py index 2a441ca..c4181af 100644 --- a/artemis/agents/video_analyzer/chunk_native.py +++ b/artemis/agents/video_analyzer/chunk_native.py @@ -45,9 +45,7 @@ async def exec_single_chunk( current_start = round(start_time, 1) if isinstance(start_time, (int, float)) else start_time current_end = round(end_time, 1) if isinstance(end_time, (int, float)) else end_time - if not isinstance(current_start, (int, float)) or not isinstance( - current_end, (int, float) - ): + if not isinstance(current_start, (int, float)) or not isinstance(current_end, (int, float)): raise ValueError("Child video analysis requires a closed numeric interval") device_id = getattr(getattr(analyzer.ctx, "device", None), "device_id", None) @@ -57,9 +55,7 @@ async def exec_single_chunk( current_end, specific_query, model_name=analyzer.model_name, - source_generation=( - active_session.generation if active_session is not None else None - ), + source_generation=(active_session.generation if active_session is not None else None), ) if claim.state == "cached": cached = ( @@ -213,9 +209,7 @@ async def _finish_failed_chunk( and media.prompt_with_context is not None ): try: - logger.warning( - "Native video chunk exhausted retries; using universal fallback" - ) + logger.warning("Native video chunk exhausted retries; using universal fallback") _va._record_llm_event( "llm_fallback", { @@ -281,10 +275,7 @@ async def _prepare_chunk_media( media.actual_start = actual_start slowdown_factor = 1.0 - if ( - current_end is not None - and (current_end - current_start) <= _va.SLOWDOWN_THRESHOLD_SECONDS - ): + if current_end is not None and (current_end - current_start) <= _va.SLOWDOWN_THRESHOLD_SECONDS: slowdown_factor = _va.SLOWDOWN_FACTOR async with _va.TRANSCODE_SEMAPHORE: @@ -319,32 +310,22 @@ def _build_chunk_prompt( warnings = analyzer.get_overlapping_warnings(start_time, end_time) warning_block = "" if warnings: - lines = [ - "WARNING: The following queries were already tried in this timeframe:\n" - ] + lines = ["WARNING: The following queries were already tried in this timeframe:\n"] for w in warnings: lines.append(f"Searched for: {w['target']} -> {w['summary']}") warning_block = "\n".join(lines) + "\n\n" duration_secs = getattr(result, "duration_seconds", None) - actual_end = ( - actual_start + duration_secs - if isinstance(duration_secs, (int, float)) - else None - ) + actual_end = actual_start + duration_secs if isinstance(duration_secs, (int, float)) else None end_str = f" to {actual_end:.1f}s" if actual_end is not None else "" warning_val = getattr(result, "warning", None) truncation_note = ( - f" (NOTE: {warning_val})" - if isinstance(warning_val, str) and warning_val - else "" + f" (NOTE: {warning_val})" if isinstance(warning_val, str) and warning_val else "" ) slowdown_note = "" if slowdown_factor != 1.0: - slowdown_note = ( - "WARNING: This video is slowed down to capture fast micro-actions.\n\n" - ) + slowdown_note = "WARNING: This video is slowed down to capture fast micro-actions.\n\n" prompt_with_context = ( "IMPORTANT CONTEXT: This video segment corresponds to" diff --git a/artemis/agents/video_analyzer/gemini_files.py b/artemis/agents/video_analyzer/gemini_files.py index e96465c..0a15bb3 100644 --- a/artemis/agents/video_analyzer/gemini_files.py +++ b/artemis/agents/video_analyzer/gemini_files.py @@ -97,9 +97,7 @@ async def cleanup_abandoned_gemini_files(client) -> None: logger.warning(f"Routine cloud maintenance skipped: {e}") -async def upload_and_poll_file( - client, compressed_path: Path, cloud_files_to_cleanup: set -) -> any: +async def upload_and_poll_file(client, compressed_path: Path, cloud_files_to_cleanup: set) -> any: """Upload a media file to the Gemini File API and poll until it is ACTIVE.""" logger.info(f"Uploading {compressed_path} to Gemini File API...") file_size_mb = compressed_path.stat().st_size / (1024 * 1024) @@ -120,9 +118,7 @@ async def upload_and_poll_file( while True: try: - f_state = await asyncio.wait_for( - client.aio.files.get(name=file.name), timeout=20 - ) + f_state = await asyncio.wait_for(client.aio.files.get(name=file.name), timeout=20) retry_count = 0 except Exception as poll_error: retry_count += 1 diff --git a/artemis/agents/video_analyzer/native_coordinator.py b/artemis/agents/video_analyzer/native_coordinator.py index e33c768..9d73f31 100644 --- a/artemis/agents/video_analyzer/native_coordinator.py +++ b/artemis/agents/video_analyzer/native_coordinator.py @@ -47,11 +47,7 @@ class _DummyResponse: types.Candidate( content=types.Content( role="model", - parts=parts - if parts - else ( - [types.Part.from_text(text=text)] if text else [] - ), + parts=parts if parts else ([types.Part.from_text(text=text)] if text else []), ) ) ] @@ -240,11 +236,7 @@ def _record_main_llm_trace( for c in contents: if hasattr(c, "model_dump"): dumped = c.model_dump() - if ( - isinstance(dumped, dict) - and "parts" in dumped - and dumped["parts"] - ): + if isinstance(dumped, dict) and "parts" in dumped and dumped["parts"]: for p in dumped["parts"]: if ( isinstance(p, dict) @@ -343,11 +335,7 @@ async def _consume_main_stream( else [types.Tool(function_declarations=tools_declaration)], safety_settings=SAFETY_SETTINGS_BLOCK_NONE, **( - { - "thinking_config": types.ThinkingConfig( - thinking_level=thinking_level - ) - } + {"thinking_config": types.ThinkingConfig(thinking_level=thinking_level)} if thinking_level else {} ), @@ -383,9 +371,7 @@ async def _consume_main_stream( chunk_text = chunk.text or "" text += chunk_text if ctx.data_engine and trace_id and chunk_text: - ctx.data_engine.stream_output( - trace_id, chunk_text, is_thinking=False - ) + ctx.data_engine.stream_output(trace_id, chunk_text, is_thinking=False) if chunk.function_calls: function_calls.extend(chunk.function_calls) @@ -422,8 +408,7 @@ async def _execute_native_tool_calls(analyzer, function_calls: list) -> list: audio_only_calls = [ fc for fc in function_calls - if (fc.name.split(":")[-1] if ":" in fc.name else fc.name) - == "analyze_audio_only" + if (fc.name.split(":")[-1] if ":" in fc.name else fc.name) == "analyze_audio_only" ] other_calls = [ fc @@ -445,9 +430,7 @@ async def _execute_native_tool_calls(analyzer, function_calls: list) -> list: return tool_response_parts -async def _run_spawn_calls( - analyzer, sub_agent_calls: list, tool_response_parts: list -) -> None: +async def _run_spawn_calls(analyzer, sub_agent_calls: list, tool_response_parts: list) -> None: """Runs spawn_sub_agent calls in parallel and appends their responses.""" logger.info(f"Executing {len(sub_agent_calls)} sub-agent calls in parallel...") @@ -469,25 +452,17 @@ async def _run_spawn_calls( error_msg = f"Sub-agent failed. Error: {result}" logger.error(error_msg) tool_response_parts.append( - types.Part.from_function_response( - name=fc.name, response={"error": error_msg} - ) + types.Part.from_function_response(name=fc.name, response={"error": error_msg}) ) else: tool_response_parts.append( - types.Part.from_function_response( - name=fc.name, response={"result": result} - ) + types.Part.from_function_response(name=fc.name, response={"result": result}) ) -async def _run_audio_calls( - analyzer, audio_only_calls: list, tool_response_parts: list -) -> None: +async def _run_audio_calls(analyzer, audio_only_calls: list, tool_response_parts: list) -> None: """Runs analyze_audio_only calls in parallel and appends their responses.""" - logger.info( - f"Executing {len(audio_only_calls)} analyze_audio_only calls in parallel..." - ) + logger.info(f"Executing {len(audio_only_calls)} analyze_audio_only calls in parallel...") async def bounded_audio_spawn(fc): async with _va.API_SEMAPHORE: @@ -505,21 +480,15 @@ async def _run_audio_calls( error_msg = f"Audio analysis failed. Error: {result}" logger.error(error_msg) tool_response_parts.append( - types.Part.from_function_response( - name=fc.name, response={"error": error_msg} - ) + types.Part.from_function_response(name=fc.name, response={"error": error_msg}) ) else: tool_response_parts.append( - types.Part.from_function_response( - name=fc.name, response={"result": result} - ) + types.Part.from_function_response(name=fc.name, response={"result": result}) ) -async def _run_other_calls( - analyzer, other_calls: list, tool_response_parts: list -) -> None: +async def _run_other_calls(analyzer, other_calls: list, tool_response_parts: list) -> None: """Runs remaining coordinator tool calls sequentially.""" for fc in other_calls: name = fc.name.split(":")[-1] if ":" in fc.name else fc.name @@ -532,15 +501,11 @@ async def _run_other_calls( args.get("start_time"), args.get("end_time") ) tool_response_parts.append( - types.Part.from_function_response( - name=name, response={"result": res} - ) + types.Part.from_function_response(name=name, response={"result": res}) ) except Exception as e: tool_response_parts.append( - types.Part.from_function_response( - name=name, response={"error": str(e)} - ) + types.Part.from_function_response(name=name, response={"error": str(e)}) ) @@ -559,9 +524,7 @@ async def _cleanup_native_run(analyzer) -> None: analyzer.cloud_files_to_cleanup.clear() if os.environ.get("KEEP_VIDEOS") or os.environ.get("ARTEMIS_DEBUG"): - logger.info( - "Skipping cleanup of local video files and directories due to debug mode." - ) + logger.info("Skipping cleanup of local video files and directories due to debug mode.") else: for path in list(analyzer.local_files_to_cleanup): try: diff --git a/artemis/agents/video_analyzer/universal_engine.py b/artemis/agents/video_analyzer/universal_engine.py index 919b35a..2fff55e 100644 --- a/artemis/agents/video_analyzer/universal_engine.py +++ b/artemis/agents/video_analyzer/universal_engine.py @@ -55,9 +55,7 @@ async def exec_single_chunk_universal( force_fallback: bool = False, ) -> str: """Executes sub-agent chunk analysis via keyframe extraction and LangChain ChatModel.""" - keyframes = _gather_universal_frames( - analyzer, raw_path, start_time, end_time, actual_start - ) + keyframes = _gather_universal_frames(analyzer, raw_path, start_time, end_time, actual_start) user_blocks, audio_block = await _build_universal_user_blocks( analyzer, keyframes, actual_start, prompt_with_context, raw_path @@ -100,8 +98,7 @@ async def exec_single_chunk_universal( end_str = f"{end_time:.1f}s" if end_time is not None else "unknown" return ( - f"[from {start_time:.1f}s to {end_str}] Summary: {final_summary}" - f" Analysis: {final_analysis}" + f"[from {start_time:.1f}s to {end_str}] Summary: {final_summary} Analysis: {final_analysis}" ) @@ -133,9 +130,7 @@ def _gather_universal_frames( max_dimension=1080, ) merged_frames = {round(timestamp, 3): data for timestamp, data in keyframes} - merged_frames.update( - {round(timestamp, 3): data for timestamp, data in targeted_frames} - ) + merged_frames.update({round(timestamp, 3): data for timestamp, data in targeted_frames}) return sorted(merged_frames.items()) @@ -218,9 +213,7 @@ async def _invoke_universal_with_visual_retry( label="Universal visual-only sub-agent", force_fallback=force_fallback, ) - span.result = ( - f"Received response (tool_calls={len(getattr(response, 'tool_calls', []))})" - ) + span.result = f"Received response (tool_calls={len(getattr(response, 'tool_calls', []))})" return response diff --git a/artemis/agents/video_analyzer/universal_tools.py b/artemis/agents/video_analyzer/universal_tools.py index 451818b..9973840 100644 --- a/artemis/agents/video_analyzer/universal_tools.py +++ b/artemis/agents/video_analyzer/universal_tools.py @@ -218,8 +218,7 @@ def build_native_tools_declaration() -> list[types.FunctionDeclaration]: "end_time": types.Schema( type=types.Type.NUMBER, description=( - "End time in seconds. Use the maximum available" - " time if not specified." + "End time in seconds. Use the maximum available time if not specified." ), ), "specific_query": types.Schema( diff --git a/artemis/agents/video_analyzer/video_analyzer.py b/artemis/agents/video_analyzer/video_analyzer.py index e618259..9e6a3d9 100644 --- a/artemis/agents/video_analyzer/video_analyzer.py +++ b/artemis/agents/video_analyzer/video_analyzer.py @@ -121,8 +121,7 @@ async def _invoke_with_retry(operation, label: str, max_attempts: int = 3): allowed_attempts = min(max_attempts, policy.max_attempts) if attempt >= allowed_attempts: logger.warning( - f"{label} exhausted {attempt} attempt(s) " - f"[{failure.category.value}]: {exc}" + f"{label} exhausted {attempt} attempt(s) [{failure.category.value}]: {exc}" ) raise delay = policy.delay_for(attempt) @@ -153,34 +152,22 @@ class VideoAnalyzer: "enable_ledger", getattr(agent_config, "enable_video_ledger", True), ) - self.chunk_size_seconds = float( - getattr(self.video_config, "chunk_size_seconds", 60.0) - ) - self.min_chunk_seconds = float( - getattr(self.video_config, "min_chunk_seconds", 4.0) - ) + self.chunk_size_seconds = float(getattr(self.video_config, "chunk_size_seconds", 60.0)) + self.min_chunk_seconds = float(getattr(self.video_config, "min_chunk_seconds", 4.0)) self.max_split_depth = int(getattr(self.video_config, "max_split_depth", 4)) - self.action_window_seconds = float( - getattr(self.video_config, "action_window_seconds", 2.0) - ) - self.dense_action_fps = float( - getattr(self.video_config, "dense_action_fps", 4.0) - ) + self.action_window_seconds = float(getattr(self.video_config, "action_window_seconds", 2.0)) + self.dense_action_fps = float(getattr(self.video_config, "dense_action_fps", 4.0)) self.max_dense_action_frames = int( getattr(self.video_config, "max_dense_action_frames", 24) ) - self.native_max_retries = int( - getattr(self.video_config, "native_max_retries", 1) - ) + self.native_max_retries = int(getattr(self.video_config, "native_max_retries", 1)) self.model_call_timeout_seconds = float( getattr(self.video_config, "model_call_timeout_seconds", 120.0) ) breaker = getattr(ctx, "_video_circuit_breaker", None) if breaker is None: breaker = VideoCircuitBreaker( - threshold=int( - getattr(self.video_config, "circuit_breaker_threshold", 3) - ), + threshold=int(getattr(self.video_config, "circuit_breaker_threshold", 3)), cooldown_seconds=float( getattr( self.video_config, @@ -311,9 +298,7 @@ class VideoAnalyzer: if resolved not in protected and directory.exists(): shutil.rmtree(directory, ignore_errors=True) except OSError as error: - logger.warning( - f"Failed to delete temporary video directory {directory}: {error}" - ) + logger.warning(f"Failed to delete temporary video directory {directory}: {error}") def _action_timestamps(self, start: float, end: float) -> list[float]: """Return test-relative timestamps for recorded actions in an interval.""" @@ -337,9 +322,7 @@ class VideoAnalyzer: logger.warning(f"Failed to load action timestamps for video sampling: {error}") return [] - def _dense_action_offsets( - self, start: float, end: float, actual_start: float - ) -> list[float]: + def _dense_action_offsets(self, start: float, end: float, actual_start: float) -> list[float]: """Build bounded, video-relative sampling offsets around mobile actions.""" if self.max_dense_action_frames <= 0: @@ -417,10 +400,10 @@ class VideoAnalyzer: utils_cfg = getattr(llm_config, "utils", None) if llm_config else None llm_cfg = getattr(utils_cfg, "video_analyzer", None) if utils_cfg else None model_str = ( - llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.7-flash" + llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.8-flash" ).lower() self.model_name = ( - llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.7-flash" + llm_cfg.model if (llm_cfg and hasattr(llm_cfg, "model")) else "gemini-3.8-flash" ) if "/" in self.model_name: self.model_name = self.model_name.split("/")[-1] @@ -557,9 +540,7 @@ class VideoAnalyzer: """Resolve an open-ended request to a stable numeric interval (or an error).""" controller = get_controller(self.ctx) async with TRANSCODE_SEMAPHORE: - metadata_result = await controller.extract_segment_metadata( - requested_start, None - ) + metadata_result = await controller.extract_segment_metadata(requested_start, None) if not metadata_result.success: return ( requested_start, @@ -567,9 +548,7 @@ class VideoAnalyzer: f"Error fetching segment metadata: {metadata_result.message}", ) duration = getattr(metadata_result, "duration_seconds", None) - actual_start = getattr( - metadata_result, "actual_start_relative_time", requested_start - ) + actual_start = getattr(metadata_result, "actual_start_relative_time", requested_start) if not isinstance(duration, (int, float)) or duration <= 0: return ( requested_start, @@ -613,9 +592,7 @@ class VideoAnalyzer: cached_results = self.blackboard.format_cached_segments( requested_start, requested_end, specific_query ) - missing = self.blackboard.missing_intervals( - requested_start, requested_end, specific_query - ) + missing = self.blackboard.missing_intervals(requested_start, requested_end, specific_query) if not missing: logger.info( "Reusing complete video-blackboard coverage for " @@ -662,15 +639,11 @@ class VideoAnalyzer: analyze_with_recovery(midpoint, ce, depth + 1), ) return left[0] + right[0], left[1] + right[1] - failure_text = ( - f"{cs:.1f}s-{ce:.1f}s [{failure.category.value}]: {error}" - ) + failure_text = f"{cs:.1f}s-{ce:.1f}s [{failure.category.value}]: {error}" logger.error(f"Video chunk terminal failure: {failure_text}") return [], [failure_text] - recovered = await asyncio.gather( - *(analyze_with_recovery(cs, ce) for cs, ce in chunks) - ) + recovered = await asyncio.gather(*(analyze_with_recovery(cs, ce) for cs, ce in chunks)) fresh_results = [item for values, _ in recovered for item in values] failed_chunks = [item for _, failures in recovered for item in failures] @@ -696,9 +669,7 @@ class VideoAnalyzer: ) -> str: from artemis.agents.video_analyzer import chunk_native - return await chunk_native.exec_single_chunk( - self, start_time, end_time, specific_query - ) + return await chunk_native.exec_single_chunk(self, start_time, end_time, specific_query) @trace(type="tool", name="analyze_audio_only") async def exec_analyze_audio_only( @@ -804,7 +775,7 @@ class VideoAnalyzer: if getattr(llm_cfg, "thinking_level", None) is not None: thinking_level = llm_cfg.thinking_level else: - self.model_name = "gemini-3.7-flash" + self.model_name = "gemini-3.8-flash" # Track files for cleanup self.local_files_to_cleanup = set() diff --git a/artemis/config/__init__.py b/artemis/config/__init__.py index e1022e8..3f1f5f7 100644 --- a/artemis/config/__init__.py +++ b/artemis/config/__init__.py @@ -18,6 +18,7 @@ from artemis.config.agent import ( AgentGlobalConfig, CheckerConfig, CommitteeConfig, + DEFAULT_VERIFICATION_LEVEL, ExplorerConfig, FlashProfileConfig, MemoryChunkingConfig, @@ -31,6 +32,10 @@ from artemis.config.agent import ( VideoAnalyzerConfig, load_agent_config, resolve_explorer_version, + VERIFICATION_LEVEL_PRESETS, + VerificationLevel, + checker_overrides_for_level, + verification_level_for_checker, ) from artemis.config.constants import ( AGENT_CONFIG_FILENAME, @@ -126,6 +131,7 @@ from artemis.config.paths import ( get_config_path, get_data_engine_db_path, get_default_traces_path, + get_env_file, get_images_dir, get_ipc_port_file, get_ls_address_file, @@ -136,6 +142,7 @@ from artemis.config.paths import ( get_test_outputs_dir, get_traces_dir, is_frozen_bundle, + is_source_checkout, ) from artemis.config.runtime import ( cleanup_temp_dir, @@ -172,10 +179,12 @@ __all__ = [ "get_app_dir", "get_traces_dir", "get_default_traces_path", + "get_env_file", "get_temp_dir", "get_config_path", "get_data_engine_db_path", "get_pause_file", + "is_source_checkout", "get_replay_dir", "get_test_data_dir", "get_test_outputs_dir", @@ -199,6 +208,7 @@ __all__ = [ "AgentGlobalConfig", "CheckerConfig", "CommitteeConfig", + "DEFAULT_VERIFICATION_LEVEL", "ExplorerConfig", "FlashProfileConfig", "MemoryChunkingConfig", @@ -212,6 +222,10 @@ __all__ = [ "VideoAnalyzerConfig", "load_agent_config", "resolve_explorer_version", + "VERIFICATION_LEVEL_PRESETS", + "VerificationLevel", + "checker_overrides_for_level", + "verification_level_for_checker", # Output Config "OutputConfig", "prepare_output_files", diff --git a/artemis/config/agent.py b/artemis/config/agent.py index 461e0e6..9f81888 100644 --- a/artemis/config/agent.py +++ b/artemis/config/agent.py @@ -117,11 +117,12 @@ class PlannerValidationConfig(BaseModel): enabled: bool = Field( default=True, - description="Whether to enable async planner validation when task plan milestones change.", - ) - similarity_threshold: float = Field( - default=0.85, - description="Difflib similarity threshold below which milestone text changes trigger re-validation.", + description=( + "Whether every top-level milestone text change is reviewed by the" + " lightweight async planner validator. The review is advisory: a" + " flagged change is never rolled back, the Operator only receives" + " the concern and its reason as feedback." + ), ) model_config = {"extra": "allow"} @@ -208,6 +209,63 @@ class CheckerConfig(BaseModel): model_config = {"extra": "allow"} +#: Coarse verification presets exposed to the CLI (``--verification-level``) +#: and the admin console (``verification_level`` on ``/api/run``). Each preset +#: is a partial :class:`CheckerConfig` override; unspecified fields keep the +#: values from ``artemis.jsonc``. ``final`` mirrors the factory layering. +VerificationLevel = Literal["off", "final", "checkpoints", "strict"] +DEFAULT_VERIFICATION_LEVEL: VerificationLevel = "final" +VERIFICATION_LEVEL_PRESETS: dict[str, dict[str, Any]] = { + # No Checker at all: the Operator self-reports completion, nothing audits it. + "off": {"enabled": False}, + # Factory layering: one exit audit against the user's goal, no midway checkpoints. + "final": {"enabled": True, "midway_checks": False, "final_check": True}, + # Every plan-declared checkpoint runs at its subgoal's completion, plus the exit audit. + "checkpoints": {"enabled": True, "midway_checks": True, "final_check": True}, + # Checkpoints + exit audit with a larger repair budget; a failed assert halts the run. + "strict": { + "enabled": True, + "midway_checks": True, + "final_check": True, + "assert_failure_policy": "halt", + "checkpoint_max_repairs": 4, + "final_check_max_attempts": 5, + "max_iterations": 30, + }, +} + + +def checker_overrides_for_level(level: str | None) -> dict[str, Any]: + """Return the :class:`CheckerConfig` field overrides for a verification level. + + Args: + level: One of ``off``, ``final``, ``checkpoints`` or ``strict`` + (case-insensitive, surrounding whitespace ignored). + + Raises: + ValueError: when ``level`` is not a known preset. + """ + key = str(level or "").strip().lower() + preset = VERIFICATION_LEVEL_PRESETS.get(key) + if preset is None: + known = ", ".join(VERIFICATION_LEVEL_PRESETS) + raise ValueError(f"Unknown verification level {level!r}; expected one of: {known}") + return dict(preset) + + +def verification_level_for_checker(checker: "CheckerConfig") -> VerificationLevel: + """Classify an effective :class:`CheckerConfig` back onto the coarse ladder. + + Used by launcher UIs to show where the configured defaults sit; the inverse of + :func:`checker_overrides_for_level` for the fields the presets control. + """ + if not checker.enabled or (not checker.midway_checks and not checker.final_check): + return "off" + if checker.midway_checks: + return "strict" if checker.assert_failure_policy == "halt" else "checkpoints" + return "final" + + class OutputterConfig(BaseModel): """Configuration specific to Outputter post-execution report synthesis agent.""" @@ -237,9 +295,12 @@ class ExplorerConfig(BaseModel): default="flash", description="Explorer version mode when main system runs under Pro profile (Graph / Operator / Validator).", ) - caching: bool = Field( - default=True, - description="Whether to enable context caching for multi-turn pro/ultra Explorer.", + caching: bool | None = Field( + default=None, + description=( + "Gemini explicit context caching for multi-turn Explorer tiers. ``None``" + " (default) uses the tier's own default: off for pro, on for ultra." + ), ) model_config = {"extra": "allow"} @@ -497,7 +558,7 @@ class MemoryChunkingConfig(BaseModel): ), ) model: str = Field( - default="gemini-3.7-flash", + default="gemini-3.8-flash", description="Model used for the chunk-level StepCapsuleLens (bands ①+②).", ) max_chunks: int = Field( @@ -555,10 +616,14 @@ class FlashProfileConfig(BaseModel): """Configuration options specific to the ⚡ Flash execution profile (FlashRunner/ReactiveRunner).""" max_turns: int = Field( - default=30, - ge=1, - le=100, - description="Maximum reactive turns for Flash execution before timeout.", + default=0, + ge=0, + description=( + "Maximum reactive turns for Flash execution; 0 (the default) means" + " unlimited. Context growth is bounded by the transcript ledger's" + " scrub edge and chunk compression, not by a turn cap, so a long" + " task simply runs until it reports its status." + ), ) explorer_mode: ExplorerVersion = Field( default="flash", @@ -585,9 +650,28 @@ class ProExplorerConfig(BaseModel): model_config = {"extra": "allow"} +class ExecutionConfig(BaseModel): + """Validator execution tiers for the Pro profile.""" + + max_burst_actions: int = Field( + default=4, + ge=2, + le=10, + description=( + "Maximum turn-ending actions the Operator may chain into one fast-action" + " burst (executed back to back without the safety net). A longer turn is" + " rejected before execution and fed back to the Operator." + ), + ) + + class ProProfileConfig(BaseModel): """Configuration options specific to the 🚀 Pro execution profile (LangGraph / Multi-Agent Closed-Loop).""" + execution: ExecutionConfig = Field( + default_factory=ExecutionConfig, + description="Validator execution tiers (vetted single action vs fast-action burst).", + ) explorer: ProExplorerConfig = Field( default_factory=ProExplorerConfig, description="Explorer perception settings for Pro profile.", @@ -602,7 +686,7 @@ class ProProfileConfig(BaseModel): ) checker: CheckerConfig = Field( default_factory=CheckerConfig, - description="Checker visual verification and rollback settings.", + description="Checker checkpoint verification and exit final review settings.", ) video_analyzer: VideoAnalyzerConfig = Field( default_factory=VideoAnalyzerConfig, @@ -630,8 +714,12 @@ class AgentGlobalConfig(BaseModel): description="UI Explorer sub-agent runtime options.", ) explorer_versions: dict[str, str] = Field( - default_factory=lambda: {"operator": "flash", "validator": "flash"}, - description="Per-agent mapping of UI explorer version (e.g. flash, pro, ultra).", + default_factory=dict, + description=( + "Advanced per-agent override of the Explorer tier (e.g." + ' {"validator": "ultra"}). Empty by default so that' + " ``explorer.flash_mode`` / ``explorer.pro_mode`` decide." + ), ) planner_validation: PlannerValidationConfig = Field( default_factory=PlannerValidationConfig, @@ -643,7 +731,7 @@ class AgentGlobalConfig(BaseModel): ) checker: CheckerConfig = Field( default_factory=CheckerConfig, - description="Checker subgoal verification and rollback runtime options.", + description="Checker checkpoint verification and exit final review runtime options.", ) outputter: OutputterConfig = Field( default_factory=OutputterConfig, @@ -792,47 +880,29 @@ def resolve_explorer_version( explicit_version: str | None = None, agent_or_profile_name: str | None = "operator", ) -> ExplorerVersion: - """Resolves active Explorer version based on explicit args, env vars, agent_config, and execution setup.""" - if explicit_version: - v = str(explicit_version).strip().lower() - if v in ("flash", "pro", "ultra"): - return v # type: ignore + """Resolves the active Explorer tier for a calling agent or profile. - env_v = os.getenv("ARTEMIS_EXPLORER_VERSION", "").strip().lower() - if env_v in ("flash", "pro", "ultra"): - return env_v # type: ignore - - if ctx: - agent_cfg = getattr(ctx, "agent_config", None) - exec_setup = getattr(ctx, "execution_setup", None) - - if agent_cfg: - if hasattr(agent_cfg, "get_explorer_version"): - return agent_cfg.get_explorer_version( - explicit_version=explicit_version, - agent_name=agent_or_profile_name, - ) - if hasattr(agent_cfg, "explorer") and hasattr(agent_cfg.explorer, "resolve"): - overrides = getattr(agent_cfg, "explorer_versions", None) - return agent_cfg.explorer.resolve( - explicit_version=explicit_version, - agent_name=agent_or_profile_name, - per_agent_overrides=overrides, - ) - - if ( - exec_setup - and hasattr(exec_setup, "explorer") - and hasattr(exec_setup.explorer, "resolve") - ): - overrides = getattr(exec_setup, "explorer_versions", None) - return exec_setup.explorer.resolve( + Precedence: explicit argument, ``ARTEMIS_EXPLORER_VERSION`` environment + override, then the user's agent configuration (per-agent override, profile + mode, default version) found on ``ctx.agent_config`` or, failing that, on + ``ctx.execution_setup``. The tier is a user setting: calling agents never + pass it themselves. + """ + for source in ( + getattr(ctx, "agent_config", None), + getattr(ctx, "execution_setup", None), + ): + explorer_cfg = getattr(source, "explorer", None) + if explorer_cfg is not None and hasattr(explorer_cfg, "resolve"): + return explorer_cfg.resolve( explicit_version=explicit_version, agent_name=agent_or_profile_name, - per_agent_overrides=overrides, + per_agent_overrides=getattr(source, "explorer_versions", None), ) - return "flash" + return ExplorerConfig().resolve( + explicit_version=explicit_version, agent_name=agent_or_profile_name + ) def load_agent_config( diff --git a/artemis/config/constants.py b/artemis/config/constants.py index 720becf..40d535c 100644 --- a/artemis/config/constants.py +++ b/artemis/config/constants.py @@ -134,7 +134,6 @@ AgentNode = Literal[ "log_reader_sub_agent", "log_analyzer", "diagnoser", - "validator_failure_analyzer", "checker", "planner_avatar", "history_analyzer_expert", @@ -142,6 +141,7 @@ AgentNode = Literal[ "explorer", "history_analyzer", "validator_pixel_safety_net", + "planner_validation", "validator", "output_analyzer", ] diff --git a/artemis/config/llm.py b/artemis/config/llm.py index 9cf2690..36915dd 100644 --- a/artemis/config/llm.py +++ b/artemis/config/llm.py @@ -116,6 +116,21 @@ class LLMWithFallback(LLM): return f"{self.provider}/{self.model} (fallback: {self.fallback})" +def lightweight_judge_default() -> "LLMWithFallback": + """Factory default for the lightweight judge nodes (pixel safety net and + planner validation): a flash-lite model at temperature 0.""" + return LLMWithFallback( + provider="google", + model="gemini-3.5-flash-lite", + temperature=0.0, + fallback=LLM( + provider="google", + model="gemini-3.1-flash-lite", + temperature=0.0, + ), + ) + + class LLMConfigUtils(BaseModel): """Configuration container for auxiliary utility agents/nodes.""" @@ -138,7 +153,6 @@ class LLMConfig(BaseModel): log_reader_sub_agent: LLMWithFallback log_analyzer: LLMWithFallback diagnoser: LLMWithFallback - validator_failure_analyzer: LLMWithFallback checker: LLMWithFallback planner_avatar: LLMWithFallback history_analyzer_expert: LLMWithFallback @@ -146,6 +160,7 @@ class LLMConfig(BaseModel): explorer: LLMWithFallback history_analyzer: LLMWithFallback | None = None validator_pixel_safety_net: LLMWithFallback | None = None + planner_validation: LLMWithFallback | None = None output_analyzer: LLMWithFallback | None = None def validate_providers(self) -> None: @@ -163,7 +178,6 @@ class LLMConfig(BaseModel): self.log_reader_sub_agent.validate_provider("LogReaderSubAgent") self.log_analyzer.validate_provider("LogAnalyzer") self.diagnoser.validate_provider("Diagnoser") - self.validator_failure_analyzer.validate_provider("ValidatorFailureAnalyzer") self.checker.validate_provider("Checker") self.planner_avatar.validate_provider("PlannerAvatar") self.history_analyzer_expert.validate_provider("HistoryAnalyzerExpert") @@ -173,6 +187,8 @@ class LLMConfig(BaseModel): self.history_analyzer.validate_provider("HistoryAnalyzer") if self.validator_pixel_safety_net: self.validator_pixel_safety_net.validate_provider("ValidatorPixelSafetyNet") + if self.planner_validation: + self.planner_validation.validate_provider("PlannerValidation") if self.output_analyzer: self.output_analyzer.validate_provider("OutputAnalyzer") @@ -192,17 +208,11 @@ class LLMConfig(BaseModel): if val is None: if item == "history_analyzer": return self.operator - elif item == "validator_pixel_safety_net": - return LLMWithFallback( - provider="google", - model="gemini-3.5-flash-lite", - temperature=0.0, - fallback=LLM( - provider="google", - model="gemini-3.1-flash-lite", - temperature=0.0, - ), - ) + elif item in ("validator_pixel_safety_net", "planner_validation"): + # Both are cheap, high-frequency judges: the pixel safety net + # runs before actions, the planner validator after every + # milestone edit. They share one lightweight default. + return lightweight_judge_default() elif item == "output_analyzer": return self.log_analyzer return val @@ -227,10 +237,10 @@ def _expand_default_into_nodes(config_dict: dict) -> dict: "default", { "provider": "google", - "model": "gemini-3.7-flash", + "model": "gemini-3.8-flash", "fallback": { "provider": "google", - "model": "gemini-3.6-flash", + "model": "gemini-3.7-flash", }, }, ) @@ -245,7 +255,6 @@ def _expand_default_into_nodes(config_dict: dict) -> dict: "log_reader_sub_agent", "log_analyzer", "diagnoser", - "validator_failure_analyzer", "checker", "planner_avatar", "history_analyzer_expert", @@ -345,7 +354,7 @@ def load_llm_config_override(path: Path | str) -> LLMConfig: if not resolved_path.exists(): try: resolved_path = get_config_path(str(path)) - except Exception: + except OSError: pass override_config_dict = {} diff --git a/artemis/config/paths.py b/artemis/config/paths.py index f21bb7a..6175a02 100644 --- a/artemis/config/paths.py +++ b/artemis/config/paths.py @@ -34,6 +34,7 @@ from artemis.config.constants import ( TEST_OUTPUTS_DIRNAME, ) from artemis.platform import platform +from artemis.resources import get_bundled_config_path # Project root directory (3 levels up from artemis/config/paths.py) ROOT_DIR = Path(__file__).resolve().parent.parent.parent @@ -45,6 +46,27 @@ def is_frozen_bundle() -> bool: return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS") +def is_source_checkout() -> bool: + """Return whether ``ROOT_DIR`` is an Artemis source checkout. + + Installed wheels also contain an ``artemis`` directory, so the repository + metadata file is the important discriminator. Runtime code must not treat + a wheel's ``site-packages`` directory as writable application state. + """ + return (ROOT_DIR / "pyproject.toml").is_file() and (ROOT_DIR / "artemis").is_dir() + + +def _use_user_app_dir() -> bool: + """Return whether mutable runtime state belongs in the PAL user directory.""" + return ( + is_frozen_bundle() + or not is_source_checkout() + or os.getenv(ENV_ARTEMIS_USE_USER_DIR) == "true" + or bool(os.getenv(ENV_ARTEMIS_APP_DIR)) + or bool(os.getenv(ENV_ANTIGRAVITY_APP_DIR)) + ) + + def get_app_dir() -> Path: """Returns the central application directory for ARTEMIS configurations, DBs, and traces. @@ -56,6 +78,11 @@ def get_app_dir() -> Path: return platform.paths.resolve_app_dir() +def get_env_file() -> Path: + """Return the canonical writable dotenv file for this installation mode.""" + return get_app_dir() / ".env" if _use_user_app_dir() else ROOT_DIR / ".env" + + def get_default_traces_path() -> Path: """Returns default traces directory. @@ -64,12 +91,7 @@ def get_default_traces_path() -> Path: env_traces = os.getenv(ENV_ARTEMIS_TRACES_DIR) if env_traces: traces_dir = Path(env_traces) - elif ( - is_frozen_bundle() - or os.getenv(ENV_ARTEMIS_USE_USER_DIR) == "true" - or os.getenv(ENV_ARTEMIS_APP_DIR) - or os.getenv(ENV_ANTIGRAVITY_APP_DIR) - ): + elif _use_user_app_dir(): traces_dir = get_app_dir() / "traces" else: traces_dir = ROOT_DIR / "traces" @@ -103,7 +125,7 @@ def get_data_engine_db_path() -> Path: def get_ipc_port_file() -> Path: """Returns the location of the IPC port synchronization file.""" app_dir_file = get_app_dir() / IPC_PORT_FILENAME - if app_dir_file.exists(): + if _use_user_app_dir() or app_dir_file.exists(): return app_dir_file return ROOT_DIR / IPC_PORT_FILENAME @@ -111,7 +133,7 @@ def get_ipc_port_file() -> Path: def get_ls_address_file() -> Path: """Returns the location of the Language Server address synchronization file.""" app_dir_file = get_app_dir() / LS_ADDRESS_FILENAME - if app_dir_file.exists(): + if _use_user_app_dir() or app_dir_file.exists(): return app_dir_file return ROOT_DIR / LS_ADDRESS_FILENAME @@ -119,7 +141,7 @@ def get_ls_address_file() -> Path: def get_server_info_file() -> Path: """Returns the location of the Artemis server metadata file.""" app_dir_file = get_app_dir() / SERVER_INFO_FILENAME - if app_dir_file.exists(): + if _use_user_app_dir() or app_dir_file.exists(): return app_dir_file return ROOT_DIR / SERVER_INFO_FILENAME @@ -131,8 +153,8 @@ def get_config_path(filename: str, default_bundled_path: Path | None = None) -> 1. Environment variable override: `ARTEMIS_` 2. Central `config/` directory: `/config/` 3. Project root directory: `/` - 4. Custom default bundled path (if provided and exists) - 5. User application/config directory via PAL: `/` or `/` + 4. User application/config directory via PAL: `/` or `/` + 5. Custom bundled path, then the immutable template included in the wheel """ env_var = f"ARTEMIS_{filename.upper().replace('.', '_').replace('-', '_')}" env_path_str = os.getenv(env_var) @@ -151,10 +173,6 @@ def get_config_path(filename: str, default_bundled_path: Path | None = None) -> if project_path.exists(): return project_path - # Check bundled fallback - if default_bundled_path and default_bundled_path.exists(): - return default_bundled_path - # Check user PAL config directory pal_config_path = platform.paths.config_dir / filename if pal_config_path.exists(): @@ -165,9 +183,16 @@ def get_config_path(filename: str, default_bundled_path: Path | None = None) -> if app_dir_path.exists(): return app_dir_path + # Explicit and wheel-bundled defaults are immutable last-resort templates. + if default_bundled_path and default_bundled_path.exists(): + return default_bundled_path + bundled_path = get_bundled_config_path(filename) + if bundled_path is not None: + return bundled_path + raise FileNotFoundError( f"Configuration file '{filename}' not found in config dir ({config_dir_path}), " - f"project root ({project_path}), or app dir ({app_dir_path})." + f"project root ({project_path}), app dir ({app_dir_path}), or bundled resources." ) @@ -177,7 +202,7 @@ GLOBAL_JETSKI_DIR = GLOBAL_APP_DIR # Backward-compatibility alias def get_pause_file() -> Path: """Returns the central pause signal file path.""" - return ROOT_DIR / PAUSE_FILENAME + return get_app_dir() / PAUSE_FILENAME if _use_user_app_dir() else ROOT_DIR / PAUSE_FILENAME def get_replay_dir() -> Path: diff --git a/artemis/config/runtime.py b/artemis/config/runtime.py index b10bab7..799cc72 100644 --- a/artemis/config/runtime.py +++ b/artemis/config/runtime.py @@ -29,6 +29,7 @@ from artemis.config.paths import ( get_ipc_port_file, get_ls_address_file, get_temp_dir, + is_source_checkout, ) from artemis.utils.logger import get_logger @@ -41,19 +42,17 @@ def read_ipc_port() -> int | None: if env_port and env_port.strip().isdigit(): return int(env_port.strip()) - candidate_paths = [ - get_temp_dir() / "artemis-ipc-port", - get_app_dir() / IPC_PORT_FILENAME, - ROOT_DIR / IPC_PORT_FILENAME, - get_ipc_port_file(), - ] + candidate_paths = [get_temp_dir() / "artemis-ipc-port", get_app_dir() / IPC_PORT_FILENAME] + if is_source_checkout(): + candidate_paths.append(ROOT_DIR / IPC_PORT_FILENAME) + candidate_paths.append(get_ipc_port_file()) for p in candidate_paths: if p.exists(): try: content = p.read_text(encoding="utf-8").strip() if content.isdigit(): return int(content) - except Exception: + except (OSError, ValueError): pass # Dynamic fallback: check local admin console status endpoint @@ -71,8 +70,8 @@ def read_ipc_port() -> int | None: ipc_port = data.get("ipc_port") if ipc_port and str(ipc_port).isdigit(): return int(ipc_port) - except Exception: - pass + except Exception as exc: + logger.debug(f"IPC port discovery via admin console status skipped: {exc}", exc_info=True) return None @@ -85,8 +84,9 @@ def write_ipc_port(port: int) -> Path: port_file, get_temp_dir() / "artemis-ipc-port", get_app_dir() / IPC_PORT_FILENAME, - ROOT_DIR / IPC_PORT_FILENAME, ] + if is_source_checkout(): + all_targets.append(ROOT_DIR / IPC_PORT_FILENAME) for target in all_targets: try: target.parent.mkdir(parents=True, exist_ok=True) @@ -103,8 +103,9 @@ def clear_ipc_port() -> None: get_ipc_port_file(), get_temp_dir() / "artemis-ipc-port", get_app_dir() / IPC_PORT_FILENAME, - ROOT_DIR / IPC_PORT_FILENAME, ] + if is_source_checkout(): + all_targets.append(ROOT_DIR / IPC_PORT_FILENAME) for target in all_targets: if target.exists(): try: diff --git a/artemis/config/settings.py b/artemis/config/settings.py index 175a5d0..b1a6a81 100644 --- a/artemis/config/settings.py +++ b/artemis/config/settings.py @@ -24,7 +24,6 @@ from pydantic_settings import BaseSettings from artemis.config.constants import ( DEFAULT_ADB_HOST, DEFAULT_ADB_PORT, - DEFAULT_EXPLORER_VERSION, DEFAULT_MODEL, DEFAULT_PROFILE, ENV_ANTHROPIC_API_KEY, @@ -37,22 +36,21 @@ from artemis.config.constants import ( ENV_OPENAI_API_KEY, ENV_VISION_API_KEY, ENV_XAI_API_KEY, - ExplorerVersion, ) from artemis.config.paths import ( GLOBAL_APP_DIR, - ROOT_DIR, - get_app_dir, get_data_engine_db_path, get_default_traces_path, + get_env_file, get_temp_dir, ) from artemis.utils.logger import get_logger -# Load environment configuration from workspace root dir and app dir if present -load_dotenv(dotenv_path=ROOT_DIR / ".env", verbose=True) +# Installed wheels load .env from the user directory, outside site-packages. +_canonical_env = get_env_file() +load_dotenv(dotenv_path=_canonical_env, verbose=True) _global_env = GLOBAL_APP_DIR / ".env" -if _global_env.exists() and _global_env.resolve() != (ROOT_DIR / ".env").resolve(): +if _global_env.exists() and _global_env.resolve() != _canonical_env.resolve(): load_dotenv(dotenv_path=_global_env, verbose=True) logger = get_logger(__name__) @@ -121,14 +119,14 @@ class Settings(BaseSettings): ARTEMIS_DEFAULT_PROFILE: str = Field(default=DEFAULT_PROFILE) ARTEMIS_DEFAULT_MODEL: str = Field(default=DEFAULT_MODEL) - # Explorer Tool Settings - EXPLORER_VERSION: ExplorerVersion = Field( - default=DEFAULT_EXPLORER_VERSION, - description="Active version mode for the UI Explorer tool ('flash', 'pro', or 'ultra')", - ) - EXPLORER_CACHING: bool = Field( - default=True, - description="Enable context caching when running multi-turn pro/ultra Explorer", + # Explorer Tool Settings (the tier itself is configured in artemis.jsonc or + # via ARTEMIS_EXPLORER_VERSION; see artemis.config.agent.ExplorerConfig) + EXPLORER_CACHING: bool | None = Field( + default=None, + description=( + "Environment-level override for Explorer context caching; unset" + " defers to the agent configuration and the tier default." + ), ) # LLM Reliability @@ -251,13 +249,11 @@ class Settings(BaseSettings): os.environ[ENV_VISION_API_KEY] = key if persist_to_env and env_key_name: - target_env_files = [ - ROOT_DIR / ".env", - get_app_dir() / ".env", - ] + target_env_files = [get_env_file()] seen_paths = set() for env_file in target_env_files: try: + env_file.parent.mkdir(parents=True, exist_ok=True) resolved = env_file.resolve() if resolved in seen_paths: continue diff --git a/artemis/context.py b/artemis/context.py index 03dddc1..c65aabe 100644 --- a/artemis/context.py +++ b/artemis/context.py @@ -122,7 +122,6 @@ class ExecutionSetup(BaseModel): assert_failure_policy: Literal["continue", "halt"] = "continue" disable_device_probes: bool = False disable_planner_validation: bool = False - planner_validation_threshold: float = 0.85 enable_committee: bool = False committee_debate_rounds: int = 2 disable_outputter: bool = False @@ -160,7 +159,7 @@ class ExecutionSetup(BaseModel): return self.explorer.pro_mode @property - def explorer_caching(self) -> bool: + def explorer_caching(self) -> bool | None: return self.explorer.caching def get_locked_app_package(self) -> str | None: @@ -185,6 +184,9 @@ class ArtemisContext(BaseModel): agent_config: Any = None adb_client: Any | None = None ui_adb_client: Any | None = None + adb_task_registry: Any | None = None + """Per-task ADB background-task registry (``artemis.tools.command_tool``). + Created lazily on first ADB tool use; killed at task end.""" execution_setup: ExecutionSetup | None = None @@ -211,7 +213,6 @@ class ArtemisContext(BaseModel): final_check_attempts: int = 0 """Number of final-check passes already executed at exit settlement.""" - task_plan_content_before: str | None = None last_validated_plan: str | None = None """Ratchet baseline for planner validation: the last plan content whose top-level milestones were validated (or the initial plan). Drift is always @@ -254,8 +255,8 @@ class ArtemisContext(BaseModel): if self.action_session is not None: try: await self.action_session.aclose() - except Exception: - pass + except Exception as exc: + logger.debug(f"Action session close failed; skipped: {exc}", exc_info=True) finally: self.action_session = None @@ -268,8 +269,8 @@ class ArtemisContext(BaseModel): await self.step_memory.flush() else: await self.step_memory.flush(timeout_seconds=0.0) - except Exception: - pass + except Exception as exc: + logger.debug(f"Step-memory flush failed; skipped: {exc}", exc_info=True) # Drain in-flight chunk capsule jobs the same way (M3): the chunk # manager persists any harvested capsules so the DB copy is complete @@ -281,8 +282,8 @@ class ArtemisContext(BaseModel): await chunker.flush() else: await chunker.flush(timeout_seconds=0.0) - except Exception: - pass + except Exception as exc: + logger.debug(f"History chunk flush failed; skipped: {exc}", exc_info=True) if self.background_tasks: tasks = list(self.background_tasks) @@ -321,8 +322,8 @@ class ArtemisContext(BaseModel): if self.mcp_client_ctx: try: await self.mcp_client_ctx.__aexit__(exc_type, exc_val, exc_tb) - except Exception: - pass + except Exception as exc: + logger.debug(f"MCP client context exit failed; skipped: {exc}", exc_info=True) finally: self.mcp_client_ctx = None self.mcp_session = None @@ -330,8 +331,8 @@ class ArtemisContext(BaseModel): if self.data_engine: try: await self.data_engine.shutdown() - except Exception: - pass + except Exception as exc: + logger.debug(f"DataEngine shutdown failed; skipped: {exc}", exc_info=True) def get_adb_client(self) -> Any: if self.adb_client is None: diff --git a/artemis/controllers/unified_controller.py b/artemis/controllers/unified_controller.py index f4b8ba4..a8a6a8b 100644 --- a/artemis/controllers/unified_controller.py +++ b/artemis/controllers/unified_controller.py @@ -61,9 +61,7 @@ class UnifiedMobileController: def __init__(self, ctx: ArtemisContext): self.ctx = ctx self._driver: BaseDeviceDriver = get_driver(ctx) - self._segment_cache: dict[ - tuple[str, int, float, float], VideoRecordingResult - ] = {} + self._segment_cache: dict[tuple[str, int, float, float], VideoRecordingResult] = {} @property def driver(self) -> BaseDeviceDriver: @@ -323,7 +321,12 @@ class UnifiedMobileController: round(end_time, 1), ) cached_res = self._segment_cache.get(cache_key) - if cached_res and cached_res.success and cached_res.video_path and cached_res.video_path.exists(): + if ( + cached_res + and cached_res.success + and cached_res.video_path + and cached_res.video_path.exists() + ): logger.info( "Reusing generation-scoped trimmed video segment for range " f"{cache_key[2]}s to {cache_key[3]}s" @@ -434,9 +437,7 @@ class UnifiedMobileController: video_id=session.video_id, generation=session.generation, sealed_until=session.sealed_until, - source_revision=( - f"{session.video_id}:{session.generation}:{round(actual_end, 3)}" - ), + source_revision=(f"{session.video_id}:{session.generation}:{round(actual_end, 3)}"), ) if cache_key is not None: self._segment_cache[cache_key] = res @@ -451,7 +452,9 @@ class UnifiedMobileController: @staticmethod def _segment_mp4_path(source_path: Path, index: int) -> Path: - return source_path.parent / ("recording.mp4" if index == 0 else f"recording_{index:03d}.mp4") + return source_path.parent / ( + "recording.mp4" if index == 0 else f"recording_{index:03d}.mp4" + ) @staticmethod async def _remux_segment_record(record: dict[str, Any]) -> bool: @@ -549,7 +552,9 @@ class UnifiedMobileController: if not crashed: await self._stop_scrcpy(session.process) self._finalize_current_segment(session, time.time()) - reason = "rotation" if rotated else "time limit" if not crashed else "recorder crash" + reason = ( + "rotation" if rotated else "time limit" if not crashed else "recorder crash" + ) logger.info(f"Rolling screen recording segment after {reason}") if not await self._start_next_recording_segment(session, display_state): return @@ -699,8 +704,12 @@ class UnifiedMobileController: session.watchdog_task.cancel() try: await session.watchdog_task - except (asyncio.CancelledError, Exception): + except asyncio.CancelledError: pass + except Exception as exc: # pylint: disable=broad-exception-caught + logger.debug( + f"Recording watchdog for {device_id} ended with an error: {exc}", exc_info=True + ) try: process = session.process @@ -711,12 +720,9 @@ class UnifiedMobileController: logger.warning(f"Error terminating scrcpy process: {proc_e}") output_path = session.local_video_path - has_existing_recording = ( - (output_path and output_path.exists()) - or any( - Path(r.get("output_path", "")).exists() or Path(r.get("path", "")).exists() - for r in session.android_segment_records - ) + has_existing_recording = (output_path and output_path.exists()) or any( + Path(r.get("output_path", "")).exists() or Path(r.get("path", "")).exists() + for r in session.android_segment_records ) if not has_existing_recording: message = "Recording file not found on disk" @@ -732,22 +738,24 @@ class UnifiedMobileController: if not record.get("conversion_scheduled"): record["conversion_scheduled"] = True session.android_conversion_tasks.append( - asyncio.create_task( - self._remux_segment_record(record) - ) + asyncio.create_task(self._remux_segment_record(record)) ) if session.android_conversion_tasks: - await asyncio.gather( - *session.android_conversion_tasks, return_exceptions=True - ) + await asyncio.gather(*session.android_conversion_tasks, return_exceptions=True) mp4_paths = [ Path(record["output_path"]) for record in session.android_segment_records - if Path(record["output_path"]).exists() and Path(record["output_path"]).stat().st_size > 0 + if Path(record["output_path"]).exists() + and Path(record["output_path"]).stat().st_size > 0 ] # Emergency fallback: if no segment MP4 was produced, attempt to remux local_video_path directly - if not mp4_paths and output_path and output_path.exists() and output_path.stat().st_size > 0: + if ( + not mp4_paths + and output_path + and output_path.exists() + and output_path.stat().st_size > 0 + ): fallback_mp4 = ( output_path.parent / "recording.mp4" if output_path.name != "recording.mp4" @@ -757,7 +765,9 @@ class UnifiedMobileController: mp4_paths.append(fallback_mp4) if not mp4_paths: - message = "Recording finalization failed; no complete browser-safe video was produced" + message = ( + "Recording finalization failed; no complete browser-safe video was produced" + ) self._record_recording_failure(session, message) remove_active_session(device_id) return VideoRecordingResult(success=False, message=message) diff --git a/artemis/core/diagnostics/adb_keys.py b/artemis/core/diagnostics/adb_keys.py index 3713f53..46eca7a 100644 --- a/artemis/core/diagnostics/adb_keys.py +++ b/artemis/core/diagnostics/adb_keys.py @@ -161,9 +161,7 @@ def inspect_adb_keys() -> AdbKeyStatus: ) -def heal_adb_keys( - adb_path: str | None = None, force: bool = False -) -> dict[str, Any]: +def heal_adb_keys(adb_path: str | None = None, force: bool = False) -> dict[str, Any]: """Auto-heal corrupted ADB authentication keys. If keys are corrupted (or if `force=True`), this function: diff --git a/artemis/core/diagnostics/probes/adb_probe.py b/artemis/core/diagnostics/probes/adb_probe.py index 498f7b2..4abd928 100644 --- a/artemis/core/diagnostics/probes/adb_probe.py +++ b/artemis/core/diagnostics/probes/adb_probe.py @@ -177,16 +177,12 @@ class AdbDeviceProbe(BaseProbe): trust_output, flags=re.IGNORECASE, ) - trust_locked = ( - trust_match.group(1).lower() in {"true", "1"} if trust_match else None - ) + trust_locked = trust_match.group(1).lower() in {"true", "1"} if trust_match else None # Prefer the modern, current-user signals. Legacy fields can coexist in # dumpsys output as stale or display-specific diagnostics and must not # override an explicit modern unlocked result. - modern_states = [ - state for state in [policy_showing, trust_locked] if state is not None - ] + modern_states = [state for state in [policy_showing, trust_locked] if state is not None] if any(modern_states): return True if modern_states: @@ -217,9 +213,7 @@ class AdbDeviceProbe(BaseProbe): stderr=subprocess.PIPE, ) try: - stdout, _ = await asyncio.wait_for( - proc.communicate(), timeout=timeout_seconds - ) + stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_seconds) except TimeoutError: proc.kill() await proc.communicate() @@ -245,9 +239,7 @@ class AdbDeviceProbe(BaseProbe): timeout_seconds: float = 2.0, ) -> bool | None: """Confirm a positive lock result so one transient sample cannot flap the UI.""" - first = await self._get_device_lock_state( - adb_path, serial, timeout_seconds=timeout_seconds - ) + first = await self._get_device_lock_state(adb_path, serial, timeout_seconds=timeout_seconds) if first is not True: return first @@ -306,9 +298,7 @@ class AdbDeviceProbe(BaseProbe): stderr=subprocess.PIPE, ) try: - stdout, _ = await asyncio.wait_for( - proc.communicate(), timeout=timeout_seconds - ) + stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_seconds) except TimeoutError: proc.kill() await proc.communicate() @@ -336,9 +326,7 @@ class AdbDeviceProbe(BaseProbe): stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) - stdout, _ = await asyncio.wait_for( - proc.communicate(), timeout=timeout_seconds - ) + stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_seconds) if proc.returncode != 0: return None @@ -363,9 +351,7 @@ class AdbDeviceProbe(BaseProbe): logger.debug(f"Failed submission-time ADB device check: {exc}") return None - async def probe_submission_readiness( - self, target_serial: str | None = None - ) -> ProbeResult: + async def probe_submission_readiness(self, target_serial: str | None = None) -> ProbeResult: """Run the minimal fail-safe device check required before enqueueing. The full diagnostics probe enriches device metadata, scans packages, @@ -417,8 +403,7 @@ class AdbDeviceProbe(BaseProbe): "installed": True, "submission_probe": True, "devices": [ - {"serial": serial, "state": state} - for serial, state in device_states + {"serial": serial, "state": state} for serial, state in device_states ], }, ) @@ -563,9 +548,7 @@ class AdbDeviceProbe(BaseProbe): "getprop", "ro.build.version.release", ) - size_task = self._run_adb_shell( - adb_path, dev.serial, "wm", "size" - ) + size_task = self._run_adb_shell(adb_path, dev.serial, "wm", "size") packages_task = self._run_adb_shell( adb_path, dev.serial, @@ -831,7 +814,9 @@ class AdbDeviceProbe(BaseProbe): # 4. Ready device available preferred_dev = None if self._target_serial: - preferred_dev = next((d for d in ready_devices if d.serial == self._target_serial), None) + preferred_dev = next( + (d for d in ready_devices if d.serial == self._target_serial), None + ) if preferred_dev and preferred_dev.is_locked is False: active_dev = preferred_dev @@ -850,9 +835,7 @@ class AdbDeviceProbe(BaseProbe): display_name = f"{display_name} ({active_dev.screen_resolution})" metadata["active_device"] = active_dev.model_dump() - metadata["lock_state_source"] = self._lock_state_sources.get( - active_dev.serial, "unknown" - ) + metadata["lock_state_source"] = self._lock_state_sources.get(active_dev.serial, "unknown") if active_dev.is_locked is not False: is_locked = active_dev.is_locked is True diff --git a/artemis/data_engine/engine.py b/artemis/data_engine/engine.py index a94ce29..5960a80 100644 --- a/artemis/data_engine/engine.py +++ b/artemis/data_engine/engine.py @@ -205,7 +205,7 @@ class DataEngine: logger.warning(f"IPC send failed; reconnecting: {exc}") try: self.ipc_socket.close() - except Exception: + except OSError: pass self.ipc_socket = None self._ipc_retry_after = 0.0 @@ -257,8 +257,8 @@ class DataEngine: ) except RuntimeError: pass - except Exception: - pass + except Exception as exc: + logger.debug(f"Cloud gateway event bridge skipped: {exc}", exc_info=True) self._send_ipc_event(event_type, data) @@ -586,7 +586,7 @@ class DataEngine: app = foreground_app or _derive_foreground_app(ui_tree) if app: extra_metadata.setdefault("foreground_app", app) - except Exception: + except (AttributeError, TypeError, ValueError): pass # M4: stamp perceptual hashes for the local screen-similarity hint @@ -601,7 +601,7 @@ class DataEngine: post_dhash = dhash_hex(post_screenshot_bytes) if post_image_name else pre_dhash if post_dhash: extra_metadata.setdefault("post_image_dhash", post_dhash) - except Exception: + except ImportError: pass step = StepRecord( @@ -626,22 +626,9 @@ class DataEngine: self.get_or_create_image(post_screenshot_bytes) self.storage.create_step(step) - # Run storage in background - try: - task = asyncio.create_task(asyncio.to_thread(_run_background_storage)) - self._pending_tasks.add(task) - task.add_done_callback(self._on_background_task_done) - except RuntimeError: - # Fallback if no event loop - def run_and_cleanup(): - _run_background_storage() - curr_thread = threading.current_thread() - if curr_thread in self._pending_threads: - self._pending_threads.remove(curr_thread) - - thread = threading.Thread(target=run_and_cleanup) - self._pending_threads.append(thread) - thread.start() + # Run storage in the shared background scheduler. It chooses an + # asyncio worker when a loop is active and a tracked thread otherwise. + self._run_in_background(_run_background_storage) logger.info(f"Recorded step {step_number} for session {self.current_session_id}") @@ -736,23 +723,7 @@ class DataEngine: payload=payload, ) - # Run storage in background - try: - asyncio.get_running_loop() - task = asyncio.create_task(asyncio.to_thread(self.storage.create_trace, trace)) - self._pending_tasks.add(task) - task.add_done_callback(self._on_background_task_done) - except RuntimeError: - # Fallback if no event loop - def run_and_cleanup(): - self.storage.create_trace(trace) - curr_thread = threading.current_thread() - if curr_thread in self._pending_threads: - self._pending_threads.remove(curr_thread) - - thread = threading.Thread(target=run_and_cleanup) - self._pending_threads.append(thread) - thread.start() + self._run_in_background(self.storage.create_trace, trace) # Publish event trace_dict = trace.model_dump() @@ -787,21 +758,7 @@ class DataEngine: error_message=error_message, ) - try: - task = asyncio.create_task(asyncio.to_thread(self.storage.create_failed_output, record)) - self._pending_tasks.add(task) - task.add_done_callback(self._pending_tasks.discard) - except RuntimeError: - - def run_and_cleanup(): - self.storage.create_failed_output(record) - curr_thread = threading.current_thread() - if curr_thread in self._pending_threads: - self._pending_threads.remove(curr_thread) - - thread = threading.Thread(target=run_and_cleanup) - self._pending_threads.append(thread) - thread.start() + self._run_in_background(self.storage.create_failed_output, record) def has_pending_operations(self) -> bool: """Check if there are any pending background tasks or threads.""" @@ -830,7 +787,7 @@ class DataEngine: if self.ipc_socket is not None: try: self.ipc_socket.close() - except Exception: + except OSError: pass self.ipc_socket = None @@ -918,8 +875,11 @@ class DataEngine: "completion_tokens": step_c, "total_tokens": step_t, } - except Exception: - pass + except Exception as exc: + logger.debug( + f"Step token usage aggregation for SSE skipped for {step_id}: {exc}", + exc_info=True, + ) return None def _on_background_task_done(self, task: asyncio.Task): @@ -927,27 +887,30 @@ class DataEngine: if not task.cancelled() and task.exception(): exc = task.exception() logger.error(f"Background task failed with exception: {exc}", exc_info=exc) - raise exc def _run_in_background(self, fn, *args): """Safely offload blocking storage/disk operation to background without stalling event loop.""" try: - task = asyncio.create_task(asyncio.to_thread(fn, *args)) - self._pending_tasks.add(task) - task.add_done_callback(self._on_background_task_done) + loop = asyncio.get_running_loop() except RuntimeError: def run_and_cleanup(): try: fn(*args) + except Exception as exc: + logger.error(f"Background storage operation failed: {exc}", exc_info=exc) finally: curr_thread = threading.current_thread() if curr_thread in self._pending_threads: self._pending_threads.remove(curr_thread) - thread = threading.Thread(target=run_and_cleanup) + thread = threading.Thread(target=run_and_cleanup, name="artemis-storage", daemon=True) self._pending_threads.append(thread) thread.start() + else: + task = loop.create_task(asyncio.to_thread(fn, *args)) + self._pending_tasks.add(task) + task.add_done_callback(self._on_background_task_done) def update_step_action( self, @@ -1008,8 +971,10 @@ class DataEngine: if step_record and getattr(step_record, "step_number", None) is not None: self._step_number_cache[sid] = int(step_record.step_number) return int(step_record.step_number) - except Exception: - pass + except Exception as exc: + logger.debug( + f"Step number lookup from storage skipped for {sid}: {exc}", exc_info=True + ) return None def get_step_record(self, step_number: int): @@ -1260,10 +1225,7 @@ class DataEngine: None, ) if parent_trace: - if parent_trace.name in ( - "operator", - "failure_analyzer", - ): + if parent_trace.name == "operator": is_relevant_llm = True t.name = parent_trace.name break @@ -1301,14 +1263,9 @@ class DataEngine: block.get("type") == "thinking" and block.get("thinking", "").strip() ): - e_type = ( - "failure_analyzer_native_thought" - if t.name == "failure_analyzer" - else "native_thought" - ) interleaved_events.append( { - "type": e_type, + "type": "native_thought", "content": (block["thinking"].strip()), } ) @@ -1316,14 +1273,9 @@ class DataEngine: block.get("type") == "text" and block.get("text", "").strip() ): - e_type = ( - "failure_analyzer_thought" - if t.name == "failure_analyzer" - else "thought" - ) interleaved_events.append( { - "type": e_type, + "type": "thought", "content": (block["text"].strip()), } ) @@ -1339,14 +1291,9 @@ class DataEngine: thought_text = safe_extract_text(thought_text) if thought_text and thought_text.strip(): - e_type = ( - "failure_analyzer_thought" - if t.name == "failure_analyzer" - else "thought" - ) interleaved_events.append( { - "type": e_type, + "type": "thought", "content": thought_text.strip(), } ) @@ -1453,23 +1400,7 @@ class DataEngine: self._bg_task_to_trace_id = {} self._bg_task_to_trace_id[task_id] = str(trace_id) - try: - task = asyncio.create_task( - asyncio.to_thread(self.storage.create_background_task, record) - ) - self._pending_tasks.add(task) - task.add_done_callback(self._pending_tasks.discard) - except RuntimeError: - - def run_and_cleanup(): - self.storage.create_background_task(record) - curr_thread = threading.current_thread() - if curr_thread in self._pending_threads: - self._pending_threads.remove(curr_thread) - - thread = threading.Thread(target=run_and_cleanup) - self._pending_threads.append(thread) - thread.start() + self._run_in_background(self.storage.create_background_task, record) self._publish("background_tasks_updated", self.get_all_background_tasks()) @@ -1484,29 +1415,13 @@ class DataEngine: if trace_id_str and hasattr(self, "_accumulated_logs"): logs = self._accumulated_logs.pop(trace_id_str, "") - try: - task = asyncio.create_task( - asyncio.to_thread( - self.storage.update_background_task_status_and_logs, - task_id, - status, - end_time, - logs, - ) - ) - self._pending_tasks.add(task) - task.add_done_callback(self._pending_tasks.discard) - except RuntimeError: - - def run_and_cleanup(): - self.storage.update_background_task_status_and_logs(task_id, status, end_time, logs) - curr_thread = threading.current_thread() - if curr_thread in self._pending_threads: - self._pending_threads.remove(curr_thread) - - thread = threading.Thread(target=run_and_cleanup) - self._pending_threads.append(thread) - thread.start() + self._run_in_background( + self.storage.update_background_task_status_and_logs, + task_id, + status, + end_time, + logs, + ) self._publish("background_tasks_updated", self.get_all_background_tasks()) diff --git a/artemis/data_engine/storage.py b/artemis/data_engine/storage.py index eaf0b61..0d6cd61 100644 --- a/artemis/data_engine/storage.py +++ b/artemis/data_engine/storage.py @@ -388,9 +388,9 @@ class StorageManager: start_time=row["start_time"], end_time=row["end_time"], local_video_path=row["local_video_path"], - status=row["status"] if "status" in row.keys() else ( - "ready" if row["end_time"] is not None else "recording" - ), + status=row["status"] + if "status" in row.keys() + else ("ready" if row["end_time"] is not None else "recording"), error=row["error"] if "error" in row.keys() else None, ) return None @@ -839,7 +839,7 @@ class StorageManager: if "action_taken" in row.keys() and row["action_taken"]: try: action_taken = json.loads(row["action_taken"]) - except Exception: + except (ValueError, TypeError): pass operator_raw_thinking = None @@ -854,7 +854,7 @@ class StorageManager: if "last_execution_result" in row.keys() and row["last_execution_result"]: try: last_execution_result = json.loads(row["last_execution_result"]) - except Exception: + except (ValueError, TypeError): pass steps.append( diff --git a/artemis/data_engine/trace.py b/artemis/data_engine/trace.py index d0cda37..78d33d0 100644 --- a/artemis/data_engine/trace.py +++ b/artemis/data_engine/trace.py @@ -59,7 +59,7 @@ def smart_serialize(obj: Any) -> str: if len(img_bytes) > 1000: sha = hashlib.sha256(img_bytes).hexdigest() return f"" - except Exception: + except (ValueError, TypeError): pass return ( f" Any: if len(img_bytes) > 1000: sha = hashlib.sha256(img_bytes).hexdigest() return f"" - except Exception: + except (ValueError, TypeError): pass return ( f" None: + """Publishes one ``checker_event`` on the DataEngine bus. + + The event is a *projection* for observers (admin console / SSE): the + append-only ledger stays the single source of truth, and publishing never + influences booking or release. Best-effort: a missing engine or a failing + subscriber is logged and ignored. + """ + engine = getattr(ctx, "data_engine", None) + publish = getattr(engine, "_publish", None) + if not callable(publish): + return + now = time.time() + try: + # ``timestamp`` mirrors the other bus events so the UI orders checker + # blocks on the same timeline as steps and traces. + publish(CHECKER_EVENT, {"ts": now, "timestamp": now, **payload}) + except Exception as e: + logger.debug(f"Failed to publish checker event: {e}") + + +def announce_checker_attempt( + ctx, + *, + attempt_id: str | None, + phase: str, + checkpoint_id: str, + subgoal_text: str, + anchor_step_id: str | None, + check_items, +) -> None: + """Publishes ``attempt_started`` from inside the Checker's traced scope. + + Called at the entry of the checker functions so the event can carry the + Checker's own ``trace_id``: the UI uses it to route the Checker's streamed + reasoning (``llm_stream``) and tool traces into the attempt's timeline + block instead of the Operator step that happens to be current. + """ + if not attempt_id: + return + try: + from artemis.data_engine.context_vars import CURRENT_TRACE_ID + + trace_id = CURRENT_TRACE_ID.get() + except (ImportError, LookupError): + trace_id = None + if trace_id: + _attempt_traces(ctx)[attempt_id] = str(trace_id) + publish_checker_event( + ctx, + { + "event": "attempt_started", + "phase": phase, + "attempt_id": attempt_id, + "checkpoint_id": checkpoint_id, + "subgoal_text": subgoal_text, + "anchor_step_id": anchor_step_id, + "trace_id": str(trace_id) if trace_id else None, + "items": check_item_payloads(check_items), + }, + ) + + +def _attempt_traces(ctx) -> dict: + """attempt_id -> the Checker's trace id for that attempt (set at announce + time). Persisted on ledger records so the UI backfill can re-attach the + attempt's tool traces (which carry that trace as parent) to its block.""" + traces = getattr(ctx, "checker_attempt_traces", None) + if not isinstance(traces, dict): + traces = {} + try: + ctx.checker_attempt_traces = traces + except (AttributeError, TypeError, ValueError): + pass + return traces + + +def attempt_trace_id(ctx, attempt_id: str | None) -> str | None: + if not attempt_id: + return None + value = _attempt_traces(ctx).get(attempt_id) + return str(value) if value else None + + +def verdict_payloads(verdicts) -> list[dict]: + """Plain-dict projection of verdict objects (pydantic or attribute bags).""" + out: list[dict] = [] + for v in verdicts or []: + out.append( + { + "item_text": str(getattr(v, "item_text", "") or ""), + "kind": str(getattr(v, "kind", "") or ""), + "status": str(getattr(v, "status", "") or ""), + "evidence": str(getattr(v, "evidence", "") or ""), + "suggestion": str(getattr(v, "suggestion", "") or ""), + } + ) + return out + + +def check_item_payloads(check_items) -> list[dict]: + return [ + {"kind": ci.kind, "text": ci.text, "when": getattr(ci, "when", "on_complete")} + for ci in (check_items or []) + ] + + # --- Ledger (append-only) ------------------------------------------------------------ @@ -279,7 +395,7 @@ def _finding_registry(ctx) -> dict: registry = {} try: ctx.checker_findings = registry - except Exception: + except (AttributeError, TypeError, ValueError): pass return registry @@ -419,6 +535,8 @@ def _record_verdicts( { "attempt_id": run.attempt_id, "checkpoint_id": run.checkpoint.checkpoint_id, + "subgoal_text": run.checkpoint.subgoal_text, + "trace_id": attempt_trace_id(ctx, run.attempt_id), "item_text": v.item_text, "kind": v.kind, "when": when_by_text.get((v.kind, v.item_text), "on_complete"), @@ -460,6 +578,8 @@ def _record_attempt_failure(ctx, run: CheckpointRun, status: str, evidence: str) { "attempt_id": run.attempt_id, "checkpoint_id": run.checkpoint.checkpoint_id, + "subgoal_text": run.checkpoint.subgoal_text, + "trace_id": attempt_trace_id(ctx, run.attempt_id), "item_text": ci.text, "kind": ci.kind, "when": ci.when, @@ -490,6 +610,35 @@ def harvest_run( findings: list[str] = [] checkpoint = run.checkpoint + def _event(status: str, verdicts_payload: list[dict], **extra) -> None: + publish_checker_event( + ctx, + { + "event": "attempt_finished", + "phase": "checkpoint", + "attempt_id": run.attempt_id, + "checkpoint_id": checkpoint.checkpoint_id, + "subgoal_text": checkpoint.subgoal_text, + "trace_id": attempt_trace_id(ctx, run.attempt_id), + "status": status, + "verdicts": verdicts_payload, + "findings": list(findings), + **extra, + }, + ) + + def _failure_verdicts(status: str, evidence: str) -> list[dict]: + return [ + { + "item_text": ci.text, + "kind": ci.kind, + "status": status, + "evidence": evidence, + "suggestion": "", + } + for ci in checkpoint.check_items + ] + if task.cancelled(): evidence = ( "attempt superseded by a newer completion of the same subgoal" @@ -497,6 +646,7 @@ def harvest_run( else "cancelled at exit settlement (settlement timeout)" ) _record_attempt_failure(ctx, run, cancelled_status, evidence) + _event(cancelled_status, _failure_verdicts(cancelled_status, evidence)) return findings exc = task.exception() @@ -512,6 +662,7 @@ def harvest_run( f" ({reason}); releasing (fail-open) but recording inconclusive." ) _record_attempt_failure(ctx, run, status, reason) + _event("error", _failure_verdicts(status, reason), error=reason) return findings report = task.result() @@ -532,6 +683,7 @@ def harvest_run( registry = _finding_registry(ctx) registry_changed = False + reverted = False if verify_failures and applicable and not user_stopped: max_repairs = int(_setting(ctx, "checkpoint_max_repairs", 2)) @@ -584,6 +736,13 @@ def harvest_run( "Assert failure under 'halt' policy: latching halt flag for exit settlement." ) + _event( + "done", + verdict_payloads(verdicts), + applicable=bool(applicable), + reverted=bool(reverted), + repairs_used=int(ctx.checkpoint_repairs.get(checkpoint.checkpoint_id, 0)), + ) return findings @@ -638,8 +797,12 @@ async def spawn_pending_checkpoints(ctx, state, anchor_step_id: str | None) -> l old_run.task.cancel() try: await old_run.task - except (asyncio.CancelledError, Exception): - pass + except (asyncio.CancelledError, Exception) as exc: + logger.debug( + f"Superseded checkpoint attempt {old_run.attempt_id} ended with " + f"{exc!r}; harvesting anyway", + exc_info=True, + ) harvest_run(ctx, state, old_run, allow_side_effects=False) seq = ctx.checkpoint_attempt_seq.get(cid, 0) + 1 @@ -658,6 +821,7 @@ async def spawn_pending_checkpoints(ctx, state, anchor_step_id: str | None) -> l anchor=anchor, goal=goal, subgoal_text=pending.subgoal_text, + attempt_id=attempt_id, ), timeout=timeout, ) @@ -699,8 +863,11 @@ async def settle_all_checkpoints(ctx, state) -> None: for t in pending: try: await t - except (asyncio.CancelledError, Exception): - pass + except (asyncio.CancelledError, Exception) as exc: + logger.debug( + f"Cancelled checkpoint attempt ended with {exc!r} at settlement; harvesting anyway", + exc_info=True, + ) for run in runs: # Settlement is bookkeeping, not a repair point: side effects off. diff --git a/artemis/graph/graph.py b/artemis/graph/graph.py index 252b3d8..f49396f 100644 --- a/artemis/graph/graph.py +++ b/artemis/graph/graph.py @@ -38,27 +38,32 @@ from artemis.agents.validator.validator import ValidatorNode from artemis.context import ArtemisContext from artemis.graph.checkpoints import ( append_ledger_record, + attempt_trace_id, compute_run_outcome, final_check_enabled, + FINAL_CHECKPOINT_ID, + FINAL_SUBGOAL_TEXT, harvest_finished_checkpoints, has_ledger_records, is_checker_note_key, + publish_checker_event, queue_checkpoints, read_ledger, revert_subgoal_status, settle_all_checkpoints, spawn_pending_checkpoints, sync_finding_lines, + verdict_payloads, write_run_outcome, ) from artemis.graph.perception import perception_node from artemis.graph.state import State from artemis.graph.visibility import strict_state from artemis.tools.command_tool import ( + HANG_GUIDANCE, manage_task_wrapper, run_adb_command_wrapper, ) -from artemis.tools.committee_tool import ask_committee_wrapper from artemis.tools.diagnostic_tool import ask_diagnoser_wrapper from artemis.tools.explorer_tool import ask_explorer_wrapper from artemis.tools.index import get_tools_from_wrappers @@ -173,6 +178,7 @@ async def execution_check_node(state: State, ctx: ArtemisContext): # Harvest finished checkpoint attempts (non-blocking: done() tasks only). check_findings = harvest_finished_checkpoints(ctx, state) + turn_metadata: dict = {} if hasattr(ctx, "planner_task") and ctx.planner_task: logger.info("Waiting for planner validation task to complete...") @@ -181,50 +187,32 @@ async def execution_check_node(state: State, ctx: ArtemisContext): planner_result = await awaited_planner_task ctx.planner_task = None - if planner_result and planner_result.get("status") == "failed": - logger.warning("Planner rejected the task plan changes.") - - # Targeted rollback: restore task_plan.md to the last content - # consistent with the validated baseline - if ctx.task_plan_content_before is not None: - task_plan_path = get_note_file_path(ctx.data_engine.base_dir, "task_plan") - if task_plan_path.exists(): - task_plan_path.write_text(ctx.task_plan_content_before, encoding="utf-8") - logger.info("Rolled back task_plan.md to its original state.") - ctx.pending_validated_plan = None - ctx.task_plan_content_before = None - - # Surface the rejection through operator_feedback — the only - # feedback channel the Operator's next prompt actually renders. - feedback = planner_result.get("feedback", "Your task plan changes were rejected.") - check_findings.append( - "[planner] Your recent modification to the top-level task" - " plan was rejected and the plan was rolled back to its" - f" previous state. Reason: {feedback} Any terminal actions" - " you outputted were not executed. Review the task goal" - " again and consider what the optimal strategy is now." + flagged = bool(planner_result and planner_result.get("status") == "failed") + if flagged: + # Advisory outcome: the plan change stays applied and the + # turn's actions run. The Operator only receives the concern + # and its reason through operator_feedback — the one feedback + # channel its next prompt renders. + logger.warning("Planner validation flagged the task plan changes.") + feedback = planner_result.get( + "feedback", "The reviewer had a concern about this plan change." ) - - # Even a rejected turn leaves a step record: the record reflects - # the decision made and the fact that its terminal actions were - # intercepted before execution. - rejected_step_id = _record_turn({"planner_rejected": True}) - - return { - "checker_success": False, - "structured_decisions": "", - "current_step_id": rejected_step_id, - "operator_feedback": check_findings or None, - } - + check_findings.append( + "[planner] Advisory review of your recent top-level plan" + f" change: {feedback} The change was NOT rolled back —" + " weigh this concern against your own observations and" + " correct the plan only if you agree." + ) + turn_metadata["planner_flagged"] = True else: - logger.info("Planner approved the task plan changes.") - # Advance the ratchet baseline: the validated content becomes - # the new reference for future drift comparisons. - if ctx.pending_validated_plan is not None: - ctx.last_validated_plan = ctx.pending_validated_plan - ctx.pending_validated_plan = None - ctx.task_plan_content_before = None + logger.info("Planner validation approved the task plan changes.") + + # Advance the ratchet baseline either way: the reviewed content is + # the new reference, so an already-flagged change is not re-flagged + # on every subsequent write. + if ctx.pending_validated_plan is not None: + ctx.last_validated_plan = ctx.pending_validated_plan + ctx.pending_validated_plan = None except asyncio.CancelledError: if not awaited_planner_task.cancelled(): raise # this node itself is being cancelled, not the awaited task @@ -234,7 +222,7 @@ async def execution_check_node(state: State, ctx: ArtemisContext): logger.error(f"Planner validation task failed: {e}") # Every Operator turn is recorded — verification never gates history. - current_step_id = _record_turn({}) + current_step_id = _record_turn(turn_metadata) # Spawn queued checkpoints only now: the just-recorded step is the evidence # anchor (its pre screenshot plus the previous turn's post state capture @@ -295,8 +283,26 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): if final_check_enabled(ctx): attempt_no = ctx.final_check_attempts + 1 ctx.final_check_attempts = attempt_no + final_attempt_id = f"final#{attempt_no}" checkpoint_timeout = float(getattr(setup, "checkpoint_timeout", 180.0) or 180.0) ledger = read_ledger(base_dir) + + def _final_event(status: str, verdicts: list[dict], **extra) -> None: + publish_checker_event( + ctx, + { + "event": "attempt_finished", + "phase": "final", + "attempt_id": final_attempt_id, + "checkpoint_id": FINAL_CHECKPOINT_ID, + "subgoal_text": FINAL_SUBGOAL_TEXT, + "trace_id": attempt_trace_id(ctx, final_attempt_id), + "status": status, + "verdicts": verdicts, + **extra, + }, + ) + try: report = await asyncio.wait_for( run_final_check( @@ -305,18 +311,22 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): plan_text=plan_text, ledger=ledger, check_items=list(snapshot.all_check_items), + attempt_id=final_attempt_id, ), timeout=checkpoint_timeout, ) except Exception as e: # Fail-open: release, but the verdict value stays inconclusive. logger.warning(f"Final check errored ({e}); releasing fail-open.") + error_verdicts: list[dict] = [] for ci in snapshot.all_check_items: append_ledger_record( base_dir, { - "attempt_id": f"final#{attempt_no}", - "checkpoint_id": "final", + "attempt_id": final_attempt_id, + "checkpoint_id": FINAL_CHECKPOINT_ID, + "subgoal_text": FINAL_SUBGOAL_TEXT, + "trace_id": attempt_trace_id(ctx, final_attempt_id), "item_text": ci.text, "kind": ci.kind, "when": ci.when, @@ -325,6 +335,16 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): "anchor_step_id": None, }, ) + error_verdicts.append( + { + "item_text": ci.text, + "kind": ci.kind, + "status": "inconclusive", + "evidence": f"final check error: {e}", + "suggestion": "", + } + ) + _final_event("error", error_verdicts, error=str(e), route="end") report = None if report is not None: @@ -340,8 +360,10 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): append_ledger_record( base_dir, { - "attempt_id": f"final#{attempt_no}", - "checkpoint_id": "final", + "attempt_id": final_attempt_id, + "checkpoint_id": FINAL_CHECKPOINT_ID, + "subgoal_text": FINAL_SUBGOAL_TEXT, + "trace_id": attempt_trace_id(ctx, final_attempt_id), "item_text": v.item_text, "kind": v.kind, "when": when, @@ -368,6 +390,13 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): " budget is exhausted (or a stop/halt is latched);" " ending with a blocked outcome." ) + _final_event( + "done", + verdict_payloads(report.verdicts), + unmet_subgoals=list(report.unmet_subgoals), + findings=list(blocked_findings), + route="blocked", + ) else: findings: list[str] = [] for text in report.unmet_subgoals: @@ -384,7 +413,22 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): ) update["operator_feedback"] = findings or None update["exit_settlement_route"] = "continue" + _final_event( + "done", + verdict_payloads(report.verdicts), + unmet_subgoals=list(report.unmet_subgoals), + findings=list(findings), + route="continue", + ) return update + else: + _final_event( + "done", + verdict_payloads(report.verdicts), + unmet_subgoals=[], + findings=[], + route="end", + ) # END path: assemble the machine-readable run outcome. records = read_ledger(base_dir) @@ -394,6 +438,15 @@ async def exit_settlement_node(state: State, ctx: ArtemisContext): # BLOCKED/partial wrap-ups carry the last findings in the metadata file. extra = {"last_findings": blocked_findings} if blocked_findings else None write_run_outcome(base_dir, outcome, extra) + publish_checker_event( + ctx, + { + "event": "run_outcome", + "phase": "outcome", + **outcome.model_dump(), + "last_findings": list(blocked_findings), + }, + ) logger.info( f"Run outcome: task_status={outcome.task_status}," f" tests(passed={outcome.tests.passed}, failed={outcome.tests.failed}," @@ -624,13 +677,6 @@ async def _process_plan_write( # A newer write supersedes the in-flight validation. ctx.planner_task.cancel() - if not milestones_changed(baseline, before): - # Last content still consistent with the baseline: preserves - # status progress made since the baseline as rollback target. - ctx.task_plan_content_before = content_before - elif ctx.task_plan_content_before is None: - ctx.task_plan_content_before = ctx.last_validated_plan - ctx.pending_validated_plan = content_after initial_goal = ( state.initial_goal if state and hasattr(state, "initial_goal") else "Unknown Goal" @@ -650,10 +696,11 @@ async def _process_plan_write( if isinstance(result, ToolMessage) and isinstance(result.content, str): result.content = ( f"{result.content}\n\nWe have applied your changes" - " to the task plan. A background verification task" - " is currently reviewing these changes. If there" - " are any issues with your modifications, you will" - " be notified shortly." + " to the task plan. A lightweight background review" + " is checking the milestone change against the" + " original goal; if it has a concern you will get an" + " advisory hint with the reason (the change stays" + " applied either way)." ) # Queue (never spawn) a checkpoint for EVERY newly completed top-level @@ -818,8 +865,10 @@ def convergence_gate( logger.warning("Assert-halt latched; routing to exit settlement.") return _terminal_route() - # Check planner-validation result - if hasattr(state, "checker_success") and not state.checker_success: + # Check planner-validation result. ``None`` means "not yet evaluated" + # (the first pass after the Planner node) — only an explicit False is a + # failure; otherwise the log would misreport the initial plan as rejected. + if getattr(state, "checker_success", None) is False: logger.info("Plan validation failed, returning to operator for retry.") return "continue" @@ -894,8 +943,7 @@ async def get_graph(ctx: ArtemisContext) -> CompiledStateGraph: ask_explorer_wrapper, recall_history_wrapper, ] - if ctx.execution_setup and ctx.execution_setup.enable_committee: - operator_specialized_wrappers.append(ask_committee_wrapper) + # ask_committee does not follow the Operator's pre-decision / turn-ending contract. operator_specialized_tools = get_tools_from_wrappers(ctx, operator_specialized_wrappers) @@ -903,18 +951,18 @@ async def get_graph(ctx: ArtemisContext) -> CompiledStateGraph: for t in operator_specialized_tools: if t.name == "run_adb_command": t.description = ( - "[EXPLORER] Executes a shell command directly on the Android" - " mobile device via ADB shell. Use this tool when normal" - " operating tools cannot achieve the goal, do not rely on it." - " You must not use this tool to bypass goals that could be" - " achieved by action tools.\nCan run synchronously or" - " transition to a background task if execution takes longer" + "[SHELL] Executes a shell command directly on the Android" + " mobile device via ADB shell. Use it when the device action" + " tools cannot achieve the goal, or to start background" + " instrumentation (performance, power, traces); never to bypass" + " goals that the action tools can achieve.\nCan run synchronously" + " or transition to a background task if execution takes longer" " than WaitMsBeforeAsync.\nSupports persistent environments" - " (environment variables) on the phone across invocations." + " (environment variables) on the phone across invocations.\n" + HANG_GUIDANCE ) elif t.name == "manage_task": t.description = ( - "[EXPLORER] Manage background ADB shell tasks launched via" + "[SHELL] Manage background ADB shell tasks launched via" " run_adb_command to monitor or terminate ongoing background" " commands." ) diff --git a/artemis/graph/state.py b/artemis/graph/state.py index 38a2e18..11fc58c 100644 --- a/artemis/graph/state.py +++ b/artemis/graph/state.py @@ -124,6 +124,18 @@ class State(BaseModel): last_execution_result: Annotated[ dict | None, "Last execution result from validator", take_last ] = None + open_incident: Annotated[ + dict | None, + "Execution incident (blocked/failed terminal action) still awaiting the" + " Operator's resolution; None once a later action executes successfully", + take_last, + ] = None + last_closed_incident: Annotated[ + dict | None, + "The incident the most recent successful terminal action closed (with" + " closed_at_step); rendered once so the Operator settles the original intent", + take_last, + ] = None subagent_calls: Annotated[ list[str], "List of sub-agent calls in current session", take_last ] = [] diff --git a/artemis/graph/visibility.py b/artemis/graph/visibility.py index 290cd59..e41cb2c 100644 --- a/artemis/graph/visibility.py +++ b/artemis/graph/visibility.py @@ -92,6 +92,10 @@ NODE_VISIBILITY: dict[str, NodeVisibility] = { # Transcript path (M2): committing the previous turn into the # ledger appends its validator result message. "last_execution_result", + # Execution incident hand-off from the Validator (open block / + # one-turn CLOSED notice in the observation tail). + "open_incident", + "last_closed_incident", }, writes={ "structured_decisions", @@ -126,6 +130,7 @@ NODE_VISIBILITY: dict[str, NodeVisibility] = { ), "validator": _vis( reads={ + "open_incident", "initial_goal", "structured_decisions", "latest_screenshot", @@ -138,6 +143,8 @@ NODE_VISIBILITY: dict[str, NodeVisibility] = { "indexed_elements", }, writes={ + "open_incident", + "last_closed_incident", "last_execution_result", # In-place turn-scoped mutations (formalized as TurnWorkspace in # Phase 2): post-action screenshot and re-derived index mappings. diff --git a/artemis/interfaces/cli/commands/batch.py b/artemis/interfaces/cli/commands/batch.py index d834c97..13f52a9 100644 --- a/artemis/interfaces/cli/commands/batch.py +++ b/artemis/interfaces/cli/commands/batch.py @@ -36,13 +36,30 @@ async def run_batch_tasks( tasks: list[str], profile_name: str = "pro", delay_seconds: float = 5.0, + verification_level: str | None = None, + explorer_pro_mode: str | None = None, ) -> None: - """Executes a list of automation tasks sequentially.""" + """Executes a list of automation tasks sequentially. + + Args: + tasks: Goals to run one after another on a single agent. + profile_name: Execution profile ('flash' or 'pro') applied to every goal. + delay_seconds: Pause between successive goals. + verification_level: Coarse Checker preset ('off', 'final', 'checkpoints', + 'strict') for the Pro profile; ignored by Flash. + explorer_pro_mode: Explorer tier ('flash', 'pro', 'ultra') behind + ``ask_explorer`` under the Pro profile; ignored by Flash. + """ if not os.environ.get("ARTEMIS_TASK_INGRESS"): os.environ["ARTEMIS_TASK_INGRESS"] = "cli" llm_config = initialize_llm_config() profile = AgentProfile(name="default", llm_config=llm_config) - config = Builders.AgentConfig.with_default_profile(profile).build() + config_builder = Builders.AgentConfig.with_default_profile(profile) + if verification_level is not None: + config_builder.with_verification_level(verification_level) + if explorer_pro_mode is not None: + config_builder.with_explorer(pro_mode=explorer_pro_mode) + config = config_builder.build() agent = Agent(config=config) await agent.init() @@ -126,6 +143,25 @@ def batch_command( help="Run in standalone embedded mode without routing through Artemis Daemon.", ), ] = False, + verification_level: Annotated[ + str | None, + typer.Option( + "--verification-level", + help=( + "Checker preset for the Pro profile, applied to every goal: 'off' (no audit)," + " 'final' (exit review only, the default), 'checkpoints' (every plan" + " checkpoint + exit review), 'strict' (checkpoints with a larger repair" + " budget; a failed assert halts)." + ), + ), + ] = None, + explorer_pro_mode: Annotated[ + str | None, + typer.Option( + "--explorer-pro-mode", + help="Explorer tier behind ask_explorer under the Pro profile ('flash', 'pro', 'ultra').", + ), + ] = None, ) -> None: """Execute multiple automation tasks in sequence.""" task_list: list[str] = [] @@ -180,23 +216,37 @@ def batch_command( console.print("[dim]Connecting to Artemis Daemon scheduler...[/dim]") is_running, base_url = ensure_daemon_running(timeout=8.0, wait_ready=True) if is_running and base_url: - console.print(f"[bold green]✓[/bold green] Artemis Daemon active at [cyan]{base_url}[/cyan]") - resp = submit_batch_to_daemon(task_list, profile=profile, base_url=base_url) + console.print( + f"[bold green]✓[/bold green] Artemis Daemon active at [cyan]{base_url}[/cyan]" + ) + resp = submit_batch_to_daemon( + task_list, + profile=profile, + verification_level=verification_level, + explorer_mode=explorer_pro_mode, + base_url=base_url, + ) if resp and resp.get("tasks"): - console.print(f"[bold green]✓[/bold green] Enqueued {len(task_list)} batch tasks in Daemon scheduler.\n") + console.print( + f"[bold green]✓[/bold green] Enqueued {len(task_list)} batch tasks in Daemon scheduler.\n" + ) results = [] for idx, t_item in enumerate(resp["tasks"], 1): sid = t_item.get("session_id") goal = t_item.get("goal") - console.print(f"[cyan]Waiting for Task {idx}/{len(task_list)}: '{goal}' (Session: {sid})...[/cyan]") + console.print( + f"[cyan]Waiting for Task {idx}/{len(task_list)}: '{goal}' (Session: {sid})...[/cyan]" + ) final_sess = wait_for_daemon_task(sid, base_url=base_url, timeout=1800.0) st = final_sess.get("status") err = final_sess.get("error") or "" - results.append({ - "task": goal, - "status": "SUCCESS" if st in ("completed", "success") else "FAILED", - "error": err, - }) + results.append( + { + "task": goal, + "status": "SUCCESS" if st in ("completed", "success") else "FAILED", + "error": err, + } + ) table = Table(title="Batch Execution Summary") table.add_column("#", justify="right", style="cyan") @@ -217,4 +267,12 @@ def batch_command( except Exception as exc: logger.debug(f"Batch daemon routing notice: {exc}") - asyncio.run(run_batch_tasks(task_list, profile_name=profile, delay_seconds=delay)) + asyncio.run( + run_batch_tasks( + task_list, + profile_name=profile, + delay_seconds=delay, + verification_level=verification_level, + explorer_pro_mode=explorer_pro_mode, + ) + ) diff --git a/artemis/interfaces/cli/commands/doctor.py b/artemis/interfaces/cli/commands/doctor.py index 4a69258..0b7b682 100644 --- a/artemis/interfaces/cli/commands/doctor.py +++ b/artemis/interfaces/cli/commands/doctor.py @@ -327,6 +327,8 @@ def doctor_command( ) # 9. Showcase UI Build Status + from artemis.resources import get_bundled_showcase_dist + base_dist = ROOT_DIR / "apps" / "showcase_ui" / "dist" candidates = [ base_dist / "frontend" / "browser" / "index.html", @@ -335,6 +337,10 @@ def doctor_command( base_dist / "index.html", ] found_showcase = next((p for p in candidates if p.exists()), None) + if found_showcase is None: + bundled_showcase = get_bundled_showcase_dist() + if bundled_showcase is not None: + found_showcase = bundled_showcase / "index.html" if found_showcase: table.add_row( "Showcase UI", diff --git a/artemis/interfaces/cli/commands/init.py b/artemis/interfaces/cli/commands/init.py index 46ae17b..9e7e7b0 100644 --- a/artemis/interfaces/cli/commands/init.py +++ b/artemis/interfaces/cli/commands/init.py @@ -21,7 +21,7 @@ from rich.console import Console from rich.panel import Panel from rich.prompt import Prompt -from artemis.config import ROOT_DIR +from artemis.config.paths import get_env_file def init_command() -> None: @@ -132,7 +132,8 @@ def init_command() -> None: ] ) - env_path = ROOT_DIR / ".env" + env_path = get_env_file() + env_path.parent.mkdir(parents=True, exist_ok=True) env_path.write_text("\n".join(env_content), encoding="utf-8") # 5. Configure MCP for AI IDEs @@ -146,7 +147,9 @@ def init_command() -> None: console.print(" [7] All supported IDEs") console.print(" [8] Skip for now") mcp_choice = Prompt.ask( - "Select IDE for MCP auto-install", choices=["1", "2", "3", "4", "5", "6", "7", "8"], default="1" + "Select IDE for MCP auto-install", + choices=["1", "2", "3", "4", "5", "6", "7", "8"], + default="1", ) if mcp_choice != "8": target_map = { diff --git a/artemis/interfaces/cli/commands/mcp.py b/artemis/interfaces/cli/commands/mcp.py index 7e33a94..c732552 100644 --- a/artemis/interfaces/cli/commands/mcp.py +++ b/artemis/interfaces/cli/commands/mcp.py @@ -215,7 +215,9 @@ def _merge_json_file( backup_path = file_path.with_suffix(file_path.suffix + ".bak") try: backup_path.write_text(raw_text, encoding="utf-8") - logger.warning(f"Existing JSON in {file_path} could not be parsed; backup created at {backup_path}") + logger.warning( + f"Existing JSON in {file_path} could not be parsed; backup created at {backup_path}" + ) except OSError as exc: logger.warning( f"Existing JSON in {file_path} could not be parsed AND the backup to" @@ -321,7 +323,9 @@ def _merge_codex_toml(file_path: Path, server_config: dict) -> bool: try: backup_path = file_path.with_suffix(file_path.suffix + ".bak") backup_path.write_text(file_path.read_text(encoding="utf-8"), encoding="utf-8") - logger.warning(f"Could not update {file_path}; backup created at {backup_path}: {e}") + logger.warning( + f"Could not update {file_path}; backup created at {backup_path}: {e}" + ) except (OSError, UnicodeDecodeError) as backup_exc: logger.warning( f"Could not update {file_path} ({e}); backup also failed: {backup_exc}" @@ -457,7 +461,17 @@ def install_rules(client: str, project_root: str) -> list[str]: return [] targets = ( - ["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw", "codex"] + [ + "antigravity", + "cursor", + "claude", + "windsurf", + "vscode", + "cline", + "roo", + "openclaw", + "codex", + ] if client == "all" else [client] ) @@ -546,7 +560,17 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[ """Auto-installs/merges ARTEMIS MCP configuration and testing rules into IDE config files across any OS.""" installed_paths: list[str] = [] targets = ( - ["antigravity", "cursor", "claude", "windsurf", "vscode", "cline", "roo", "openclaw", "codex"] + [ + "antigravity", + "cursor", + "claude", + "windsurf", + "vscode", + "cline", + "roo", + "openclaw", + "codex", + ] if client == "all" else [client] ) @@ -561,9 +585,7 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[ "mcpServers" ]["artemis"] jetski_path = Path.home() / ".gemini" / "jetski" / "mcp_config.json" - antigravity_legacy_path = ( - Path.home() / ".gemini" / "antigravity" / "mcp_config.json" - ) + antigravity_legacy_path = Path.home() / ".gemini" / "antigravity" / "mcp_config.json" config_path = Path.home() / ".gemini" / "config" / "mcp_config.json" if _merge_json_file(jetski_path, "artemis", legacy_server_cfg): installed_paths.append(str(jetski_path)) @@ -574,7 +596,13 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[ elif target in ("claude", "claude_code", "claude_desktop"): server_cfg = snippet["mcpServers"]["artemis"] if sys.platform == "darwin": - claude_path = Path.home() / "Library" / "Application Support" / "Claude" / "claude_desktop_config.json" + claude_path = ( + Path.home() + / "Library" + / "Application Support" + / "Claude" + / "claude_desktop_config.json" + ) elif sys.platform == "win32": appdata = os.environ.get("APPDATA") or str(Path.home() / "AppData" / "Roaming") claude_path = Path(appdata) / "Claude" / "claude_desktop_config.json" @@ -624,10 +652,7 @@ def install_mcp_config(client: str, python_exe: str, project_root: str) -> list[ elif target in ("roo", "roo_code"): server_cfg = snippet["mcpServers"]["artemis"] roo_settings_dir = ( - _get_vscode_user_dir() - / "globalStorage" - / "rooveterinaryinc.roo-cline" - / "settings" + _get_vscode_user_dir() / "globalStorage" / "rooveterinaryinc.roo-cline" / "settings" ) roo_paths = ( roo_settings_dir / "mcp_settings.json", @@ -740,10 +765,14 @@ def mcp_command( ) raise typer.Exit(1) installed_paths = install_mcp_config(client, python_exe, project_root) - console.print("[bold green]✔ Successfully installed ARTEMIS MCP server configuration & rules to:[/bold green]") + console.print( + "[bold green]✔ Successfully installed ARTEMIS MCP server configuration & rules to:[/bold green]" + ) for path in installed_paths: console.print(f" • [cyan]{path}[/cyan]") - console.print("\n[dim]Please restart or reload your IDE window to activate the Artemis MCP tools.[/dim]") + console.print( + "\n[dim]Please restart or reload your IDE window to activate the Artemis MCP tools.[/dim]" + ) raise typer.Exit(0) if generate_config: @@ -762,9 +791,7 @@ def mcp_command( "windsurf (~/.codeium/windsurf/mcp_config.json)": _get_config_snippet( "windsurf", python_exe, project_root ), - "vscode (mcp.json)": _get_config_snippet( - "vscode", python_exe, project_root - ), + "vscode (mcp.json)": _get_config_snippet("vscode", python_exe, project_root), "cline (cline_mcp_settings.json)": _get_config_snippet( "cline", python_exe, project_root ), @@ -781,7 +808,9 @@ def mcp_command( json_str = json.dumps(all_configs, indent=2) syntax_language = "json" elif client == "codex": - server_cfg = _get_config_snippet("codex", python_exe, project_root)["mcp_servers"]["artemis"] + server_cfg = _get_config_snippet("codex", python_exe, project_root)["mcp_servers"][ + "artemis" + ] json_str = _codex_toml_block(server_cfg) syntax_language = "toml" else: @@ -794,9 +823,7 @@ def mcp_command( console.print(syntax) raise typer.Exit(0) - threading.Thread( - target=start_awake_service, daemon=True, name="artemis-awake-init" - ).start() + threading.Thread(target=start_awake_service, daemon=True, name="artemis-awake-init").start() try: st = server_type.lower() if st in ("agent", "mobile", "artemis", "default"): diff --git a/artemis/interfaces/cli/commands/run.py b/artemis/interfaces/cli/commands/run.py index 34ba6e8..91a5d04 100644 --- a/artemis/interfaces/cli/commands/run.py +++ b/artemis/interfaces/cli/commands/run.py @@ -22,7 +22,7 @@ from typing import Annotated from adbutils import AdbClient from langchain_core.callbacks.base import Callbacks -from artemis.config import initialize_llm_config, settings +from artemis.config import checker_overrides_for_level, initialize_llm_config, settings from artemis.utils.startup_progress import publish_startup_progress from artemis import Agent, Builders from artemis.sdk.types.task import AgentProfile @@ -58,6 +58,7 @@ async def execute_task( explorer_version: str | None = None, explorer_flash_mode: str | None = None, explorer_pro_mode: str | None = None, + verification_level: str | None = None, ) -> None: """Executes a single mobile automation task end-to-end. @@ -73,21 +74,23 @@ async def execute_task( app_path: Optional local APK path to install prior to execution. enable_planner_validation: Explicit override to enable/disable async planner validation. enable_committee: Explicit override to enable/disable Multi-Agent Committee tool. - explorer_version: Override default Explorer version mode ('flash', 'pro', or 'ultra'). - explorer_flash_mode: Override Explorer version mode for Flash execution profile. - explorer_pro_mode: Override Explorer version mode for Pro execution profile. + explorer_version: Override the fallback Explorer tier ('flash', 'pro', or 'ultra'). + explorer_flash_mode: Override the Explorer tier for the Flash execution profile. + explorer_pro_mode: Override the Explorer tier for the Pro execution profile. + verification_level: Coarse Checker preset ('off', 'final', 'checkpoints', + 'strict'); applied before the explicit ``enable_checker`` switch. """ effective_sid = ( - session_id - or os.getenv("ARTEMIS_SESSION_ID") - or os.getenv("ARTEMIS_CLOUD_SESSION_ID") + session_id or os.getenv("ARTEMIS_SESSION_ID") or os.getenv("ARTEMIS_CLOUD_SESSION_ID") ) if effective_sid: os.environ["ARTEMIS_SESSION_ID"] = str(effective_sid) if not os.environ.get("ARTEMIS_TASK_INGRESS"): os.environ["ARTEMIS_TASK_INGRESS"] = "cli" publish_startup_progress( - "configuration", "Loading the run configuration", session_id=str(effective_sid) if effective_sid else None + "configuration", + "Loading the run configuration", + session_id=str(effective_sid) if effective_sid else None, ) llm_config = initialize_llm_config() @@ -103,6 +106,9 @@ async def execute_task( if enable_committee is not None: config.with_committee(enabled=enable_committee) + if verification_level is not None: + config.with_checker(**checker_overrides_for_level(verification_level)) + if enable_checker is not None: config.with_checker(enabled=enable_checker) @@ -129,16 +135,20 @@ async def execute_task( if settings.ADB_HOST: config.with_adb_server(host=settings.ADB_HOST, port=settings.ADB_PORT) - target_serial = device_serial or settings.ADB_DEVICE_SERIAL or os.environ.get("ADB_DEVICE_SERIAL") + target_serial = ( + device_serial or settings.ADB_DEVICE_SERIAL or os.environ.get("ADB_DEVICE_SERIAL") + ) if not target_serial: try: from artemis.runtime import device_pool + target_serial = device_pool.select_device() except Exception: target_serial = None if target_serial: from artemis.context import DevicePlatform + config.for_device(DevicePlatform.ANDROID, target_serial) if graph_config_callbacks: @@ -238,7 +248,7 @@ def run_command( bool | None, typer.Option( "--enable-planner-validation/--disable-planner-validation", - help="Enable or disable async Planner validation when task plan milestones change.", + help="Enable or disable the advisory Planner review of task plan milestone edits (hint only, never rolled back).", ), ] = None, enable_committee: Annotated[ @@ -252,7 +262,7 @@ def run_command( bool | None, typer.Option( "--enable-checker/--disable-checker", - help="Enable or disable visual subgoal verification and rollback by the Checker agent.", + help="Enable or disable the Checker (plan checkpoint verification and exit final review).", ), ] = None, enable_step_summarizer: Annotated[ @@ -280,21 +290,36 @@ def run_command( str | None, typer.Option( "--explorer-version", - help="Default Explorer version mode ('flash' for 1-shot detection, 'pro' for 3-turn ReAct, 'ultra' for deep pixel reasoning).", + help=( + "Fallback Explorer tier when no profile knob applies: 'flash' (one-shot" + " detection), 'pro' (3-turn reasoning loop) or 'ultra' (8-turn loop with" + " zoom, OCR and image processing)." + ), ), ] = None, explorer_flash_mode: Annotated[ str | None, typer.Option( "--explorer-flash-mode", - help="Explorer version mode when running under Flash profile (FlashRunner).", + help="Explorer tier behind ask_explorer under the Flash profile ('flash', 'pro', 'ultra').", ), ] = None, explorer_pro_mode: Annotated[ str | None, typer.Option( "--explorer-pro-mode", - help="Explorer version mode when running under Pro profile (Operator/Validator).", + help="Explorer tier behind ask_explorer under the Pro profile ('flash', 'pro', 'ultra').", + ), + ] = None, + verification_level: Annotated[ + str | None, + typer.Option( + "--verification-level", + help=( + "Checker preset for the Pro profile: 'off' (no audit), 'final' (exit review" + " only, the default), 'checkpoints' (every plan checkpoint + exit review)," + " 'strict' (checkpoints with a larger repair budget; a failed assert halts)." + ), ), ] = None, device_serial: Annotated[ @@ -347,7 +372,9 @@ def run_command( is_running, base_url = ensure_daemon_running(timeout=8.0, wait_ready=True) if is_running and base_url: target_sid = session_id or str(uuid.uuid4()) - console.print(f"[bold green]✓[/bold green] Artemis Daemon active at [cyan]{base_url}[/cyan]") + console.print( + f"[bold green]✓[/bold green] Artemis Daemon active at [cyan]{base_url}[/cyan]" + ) resp = submit_task_to_daemon( goal=goal, profile=profile or "pro", @@ -361,13 +388,17 @@ def run_command( base_url=base_url, ) if resp and resp.get("tasks"): - console.print(f"[bold green]✓[/bold green] Task scheduled in unified queue (Session: [cyan]{target_sid}[/cyan])") + console.print( + f"[bold green]✓[/bold green] Task scheduled in unified queue (Session: [cyan]{target_sid}[/cyan])" + ) console.print(f"[dim]Live dashboard & replay: {base_url}[/dim]\n") def on_status(sess_info): st = sess_info.get("status") if st == "queued": - console.print("[yellow]⏳ Task queued in scheduler, waiting for device...[/yellow]") + console.print( + "[yellow]⏳ Task queued in scheduler, waiting for device...[/yellow]" + ) elif st == "running": console.print("[green]▶ Task executing on mobile device...[/green]") @@ -380,7 +411,9 @@ def run_command( ) final_st = final_res.get("status") if final_st in ("completed", "success"): - console.print("\n[bold green]✅ Task completed successfully![/bold green]") + console.print( + "\n[bold green]✅ Task completed successfully![/bold green]" + ) return else: err = final_res.get("error") or final_res.get("explanation") or "" @@ -392,14 +425,20 @@ def run_command( console.print("[yellow]Task cancelled.[/yellow]") raise SystemExit(130) else: - console.print("[yellow]Warning: Could not enqueue task to Daemon. Falling back to local execution...[/yellow]") + console.print( + "[yellow]Warning: Could not enqueue task to Daemon. Falling back to local execution...[/yellow]" + ) else: - console.print("[yellow]Warning: Artemis Daemon could not be started. Falling back to local execution...[/yellow]") + console.print( + "[yellow]Warning: Artemis Daemon could not be started. Falling back to local execution...[/yellow]" + ) except SystemExit: raise except Exception as exc: logger.debug(f"Daemon dispatch error: {exc}") - console.print(f"[yellow]Daemon routing notice: {exc}. Falling back to local execution...[/yellow]") + console.print( + f"[yellow]Daemon routing notice: {exc}. Falling back to local execution...[/yellow]" + ) adb_client = None try: @@ -448,9 +487,13 @@ def run_command( explorer_version=explorer_version, explorer_flash_mode=explorer_flash_mode, explorer_pro_mode=explorer_pro_mode, + verification_level=verification_level, ) ) - except KeyboardInterrupt: + except (KeyboardInterrupt, asyncio.CancelledError): + # KeyboardInterrupt: Ctrl+C / SIGTERM. CancelledError: the daemon asked + # this worker to stop through a cancel marker and the Agent cancelled + # its own task so the recording and trace could be finalized. cancelled = True except Exception as e: err_msg = str(e) diff --git a/artemis/interfaces/cli/commands/server_lifecycle.py b/artemis/interfaces/cli/commands/server_lifecycle.py index 7a1ab59..923fced 100644 --- a/artemis/interfaces/cli/commands/server_lifecycle.py +++ b/artemis/interfaces/cli/commands/server_lifecycle.py @@ -128,7 +128,9 @@ def restart_command( existing_pids = find_server_pids(port) if existing_pids: pid_str = ", ".join(map(str, existing_pids)) - console.print(f" [yellow]🛑 Terminating running Artemis instance (PID: {pid_str})...[/yellow]") + console.print( + f" [yellow]🛑 Terminating running Artemis instance (PID: {pid_str})...[/yellow]" + ) else: console.print(f" [dim]ℹ No active Artemis server found on port {port}.[/dim]") @@ -191,7 +193,9 @@ def restart_command( return # Foreground mode (--foreground / --reload) - console.print(" [cyan]🚀 Launching unified Artemis Showcase UI in current terminal...[/cyan]\n") + console.print( + " [cyan]🚀 Launching unified Artemis Showcase UI in current terminal...[/cyan]\n" + ) ui_command(host=host, port=port, open_browser=open_browser, reload=reload) @@ -214,7 +218,9 @@ def stop_command( existing_pids = find_server_pids(port) if existing_pids: pid_str = ", ".join(map(str, existing_pids)) - console.print(f" [yellow]🛑 Stopping Artemis server on port {port} (PID: {pid_str})...[/yellow]") + console.print( + f" [yellow]🛑 Stopping Artemis server on port {port} (PID: {pid_str})...[/yellow]" + ) else: console.print(f" [dim]Scanning port {port}...[/dim]") @@ -262,7 +268,11 @@ def status_command( status = get_server_status(port=port) if status["running"]: - pids_str = ", ".join(map(str, status["pids"])) if status["pids"] else str(status["active_pid"] or "Unknown") + pids_str = ( + ", ".join(map(str, status["pids"])) + if status["pids"] + else str(status["active_pid"] or "Unknown") + ) uptime_str = _format_uptime(status["uptime_seconds"]) metadata = status.get("metadata") or {} @@ -276,13 +286,17 @@ def status_command( pass table = Table(show_header=False, box=None, padding=(0, 2)) - table.add_row("[bold cyan]Status[/bold cyan]", "[bold green]● ONLINE / RUNNING[/bold green]") + table.add_row( + "[bold cyan]Status[/bold cyan]", "[bold green]● ONLINE / RUNNING[/bold green]" + ) table.add_row("[bold cyan]PID(s)[/bold cyan]", f"[yellow]{pids_str}[/yellow]") table.add_row("[bold cyan]Port[/bold cyan]", f"[white]{port}[/white]") table.add_row("[bold cyan]Uptime[/bold cyan]", f"[white]{uptime_str}[/white]") table.add_row("[bold cyan]Started At[/bold cyan]", f"[dim]{started_at_str}[/dim]") table.add_row("[bold cyan]Showcase UI[/bold cyan]", f"[cyan]{status['url']}[/cyan]") - table.add_row("[bold cyan]Admin Debug[/bold cyan]", f"[magenta]{status['admin_url']}[/magenta]") + table.add_row( + "[bold cyan]Admin Debug[/bold cyan]", f"[magenta]{status['admin_url']}[/magenta]" + ) if metadata.get("cwd"): table.add_row("[bold cyan]Workspace[/bold cyan]", f"[dim]{metadata['cwd']}[/dim]") @@ -295,7 +309,9 @@ def status_command( expand=False, ) ) - console.print(" [dim]💡 To restart: [bold]artemis restart[/bold] | To stop: [bold]artemis stop[/bold][/dim]\n") + console.print( + " [dim]💡 To restart: [bold]artemis restart[/bold] | To stop: [bold]artemis stop[/bold][/dim]\n" + ) else: console.print( Panel( @@ -307,4 +323,6 @@ def status_command( expand=False, ) ) - console.print(" [dim]💡 To start: [bold]artemis ui[/bold] or [bold]./start.sh[/bold][/dim]\n") + console.print( + " [dim]💡 To start: [bold]artemis ui[/bold] or [bold]./start.sh[/bold][/dim]\n" + ) diff --git a/artemis/interfaces/cli/commands/ui.py b/artemis/interfaces/cli/commands/ui.py index 86c6e1b..e722927 100644 --- a/artemis/interfaces/cli/commands/ui.py +++ b/artemis/interfaces/cli/commands/ui.py @@ -14,6 +14,7 @@ """UI Launch and Web Console CLI Command (artemis ui).""" +import logging from pathlib import Path import shutil import subprocess @@ -28,6 +29,8 @@ from rich.console import Console from rich.panel import Panel import typer +logger = logging.getLogger(__name__) + def running_via_console_script_shim() -> bool: """Return True when this CLI was launched through a console-script ``.exe`` shim. @@ -87,14 +90,21 @@ def _showcase_build_required(showcase_dir: Path) -> bool: def ensure_showcase_built(console: Console) -> None: """Rebuild the Angular Showcase UI when its sources are newer than the dist build.""" from artemis.config.paths import ROOT_DIR + from artemis.resources import get_bundled_showcase_dist showcase_dir = ROOT_DIR / "apps" / "showcase_ui" + # An installed wheel has no frontend source tree; its immutable build is + # prepared during packaging and must never trigger npm at runtime. + if not showcase_dir.is_dir() and get_bundled_showcase_dist() is not None: + return if not _showcase_build_required(showcase_dir): return npm_executable = _resolve_npm_executable() if not npm_executable: return - console.print(" [yellow]🎨 Showcase UI sources changed. Compiling Angular Showcase UI...[/yellow]") + console.print( + " [yellow]🎨 Showcase UI sources changed. Compiling Angular Showcase UI...[/yellow]" + ) try: subprocess.run([npm_executable, "install", "--silent"], cwd=showcase_dir, check=True) subprocess.run([npm_executable, "run", "build"], cwd=showcase_dir, check=True) @@ -113,8 +123,8 @@ def load_session_reconciler() -> None: """ try: import apps.admin_console.database.repositories.session_repository # noqa: F401 - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + logger.debug("Admin console session reconciler not loaded: %s", exc, exc_info=True) def _poll_and_open_browser(url: str, stop_event: threading.Event, timeout: float = 15.0) -> None: @@ -189,7 +199,9 @@ def ui_command( pids = find_server_pids(port) pid_str = f" (PID: {', '.join(map(str, pids))})" if pids else "" if sys.stdin.isatty(): - console.print(f"\n [yellow]⚠ Port {port} is already in use by an active Artemis server{pid_str}.[/yellow]") + console.print( + f"\n [yellow]⚠ Port {port} is already in use by an active Artemis server{pid_str}.[/yellow]" + ) console.print(" [dim]Choose an action:[/dim]") console.print(" [bold]1[/bold] Open browser (default)") console.print(" [bold]2[/bold] Restart server (stop existing and start here)") @@ -199,7 +211,9 @@ def ui_command( except (KeyboardInterrupt, EOFError): choice = "1" if choice == "2": - console.print(f"\n [yellow]🔄 Terminating previous Artemis instance on port {port}...[/yellow]") + console.print( + f"\n [yellow]🔄 Terminating previous Artemis instance on port {port}...[/yellow]" + ) load_session_reconciler() stop_server(port=port, timeout=12.0) elif choice == "3": @@ -230,21 +244,23 @@ def ui_command( ) ) console.print(Panel(msg, title="🚀 Web Server Active", border_style="cyan", expand=False)) - # Ensure .env exists in workspace root - from artemis.config.paths import ROOT_DIR + # Ensure the canonical writable .env exists. Installed wheels must never + # attempt to mutate their site-packages directory. + from artemis.config.paths import ROOT_DIR, get_env_file, is_source_checkout - env_file = ROOT_DIR / ".env" + env_file = get_env_file() env_example = ROOT_DIR / ".env.example" if not env_file.exists(): - if env_example.exists(): + env_file.parent.mkdir(parents=True, exist_ok=True) + if is_source_checkout() and env_example.exists(): try: env_file.write_text(env_example.read_text(encoding="utf-8"), encoding="utf-8") - except Exception: + except (OSError, UnicodeError): pass else: try: env_file.touch() - except Exception: + except OSError: pass ensure_showcase_built(console) diff --git a/artemis/interfaces/cli/main.py b/artemis/interfaces/cli/main.py index 0e0b1b8..fe42218 100644 --- a/artemis/interfaces/cli/main.py +++ b/artemis/interfaces/cli/main.py @@ -14,9 +14,9 @@ """ARTEMIS Unified Command Line Interface (CLI).""" -import importlib.metadata from typing import Annotated +from artemis._version import __version__ from artemis.interfaces.cli.commands.batch import batch_command from artemis.interfaces.cli.commands.doctor import doctor_command from artemis.interfaces.cli.commands.init import init_command @@ -67,14 +67,10 @@ app.add_typer(trace_app, name="trace", help="Inspect and query execution traces. def version_callback(value: bool): if value: - try: - ver = importlib.metadata.version("artemis") - except Exception: - ver = "3.6.3" console = Console() console.print( Panel( - f"[bold cyan]Artemis Agent Platform[/bold cyan] v{ver}\n" + f"[bold cyan]Artemis Agent Platform[/bold cyan] v{__version__}\n" "[dim]Autonomous Multimodal Mobile AI Engine[/dim]", title="☕ Artemis", expand=False, diff --git a/artemis/llm/router.py b/artemis/llm/router.py index 5a48a6e..be89df9 100644 --- a/artemis/llm/router.py +++ b/artemis/llm/router.py @@ -365,5 +365,3 @@ class ModelFactory: else: raise ValueError(f"Unsupported model provider: {provider}") - - diff --git a/artemis/mcp/action_executor.py b/artemis/mcp/action_executor.py index 54c1ec8..6e274b5 100644 --- a/artemis/mcp/action_executor.py +++ b/artemis/mcp/action_executor.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""MCP-backed action executor for FlashRunner and FailureAnalyzer. +"""MCP-backed action executor for the FlashRunner. Same public contract as the legacy ``MobileActionExecutor`` -- ``execute(name, args, tool_call_id, state) -> ToolExecutionResult`` -- but execution @@ -23,7 +23,7 @@ Kept agent-side (by design, not omission): argument normalization, element-index resolution against LangGraph ``State``, smart-swipe resolution, act-then-observe capture and ``State`` write-back, tracing (a ``ContextVar`` cannot cross the server's task boundary -- see the plan's R2), and the non-device tools (``read_note``, -``list_notes``, ``ask_explorer``). +``list_notes``, ``ask_explorer``, ``video_analyzer``, ``recall_history``). Status is carried by ``ActionResult.ok`` -- the historical substring sniffing on ``"Error"``/``"Failed"`` is gone, so typing the literal text "Failed" into a field no @@ -37,7 +37,6 @@ import json from pathlib import Path from typing import Any -from artemis.config import resolve_explorer_version from artemis.context import ArtemisContext from artemis.controllers.unified_controller import UnifiedMobileController from artemis.data_engine.trace import TraceSpan @@ -69,6 +68,12 @@ logger = get_logger(__name__) __all__ = ["McpActionExecutor"] +#: Non-device tools executed agent-side (never behind the action server). +AGENT_TOOL_NAMES: frozenset[str] = frozenset( + {"read_note", "list_notes", "ask_explorer", "video_analyzer", "recall_history"} +) + + class _ArgError(ValueError): """Argument-translation failure whose message is already fully formatted.""" @@ -81,10 +86,15 @@ class McpActionExecutor: ctx: ArtemisContext, controller: UnifiedMobileController | None = None, actuator: AdbActuator | None = None, + agent_name: str = "validator", ): self.ctx = ctx self.actuator = actuator or getattr(ctx, "actuator", None) or AdbActuator(ctx, controller) self.controller = self.actuator.controller + # Identifies the calling agent / profile to the Explorer tier resolver + # (``explorer.flash_mode`` vs ``explorer.pro_mode``); the agent itself + # never sees or chooses the tier. + self.agent_name = agent_name self._session: ActionSession | None = None @property @@ -109,7 +119,7 @@ class McpActionExecutor: """Executes the named tool and wraps the outcome for the calling agent.""" raw_name = name.split(":")[-1] if ":" in name else name - if raw_name in ("read_note", "list_notes", "ask_explorer"): + if raw_name in AGENT_TOOL_NAMES: return await self._execute_agent_tool(raw_name, name, args, tool_call_id, state) if raw_name in self.action_tool_names: @@ -405,10 +415,10 @@ class McpActionExecutor: sequence_str = sequence.strip() try: sequence = json.loads(sequence_str) - except Exception: + except ValueError: try: sequence = ast.literal_eval(sequence_str) - except Exception: + except (ValueError, SyntaxError, TypeError, RecursionError): pass if not isinstance(sequence, (list, tuple)): @@ -508,6 +518,8 @@ class McpActionExecutor: ) -> ToolExecutionResult: span = TraceSpan(name=raw_name, trace_type="tool", ctx=self.ctx) span.payload = {"args": args} + ok: bool | None = None + blocks: list[dict[str, Any]] | None = None with span: if raw_name == "read_note": text = await self._read_note( @@ -515,21 +527,42 @@ class McpActionExecutor: ) elif raw_name == "list_notes": text = await self._list_notes() + elif raw_name == "video_analyzer": + text, ok = await self._video_analyzer( + args.get("time_description") or args.get("TimeDescription") or "", + args.get("purpose") or args.get("Purpose") or "", + ) + elif raw_name == "recall_history": + text, blocks = await self._recall_history(args) + ok = True else: - text = await self._ask_explorer( + text, ok = await self._ask_explorer( + # ``task_description`` is the pre-contract argument name + # kept for old MCP clients. args.get("query") or args.get("task_description") or "", args.get("context_feedback"), state, ) span.result = {"outcome": text} + if ok is False: + span.status = "failed" + span.error = text - # These texts come from our own failure formatters, so the historical - # "Error" marker check is reliable here (unlike free-form device output). + # Note texts come from our own failure formatters, so the historical + # "Error" marker check is reliable there (unlike free-form device + # output); the Explorer and the video analyzer report their status + # explicitly and a history recall is a lookup whose "no match" answer + # is not an error. + if ok is None: + ok = "Error" not in (text or "") return ToolExecutionResult( tool_call_id=tool_call_id, tool_name=name, - status="error" if "Error" in (text or "") else "success", + status="success" if ok else "error", text_summary=text, + # Content blocks (recalled screenshots) ride along for callers that + # forward multimodal tool results; the text summary stays complete. + raw_result=blocks, ) async def _read_note(self, key: str, start_line: int | None, end_line: int | None) -> str: @@ -550,31 +583,61 @@ class McpActionExecutor: except Exception as e: return format_list_notes_failure(str(e)) - async def _ask_explorer(self, query: str, context_feedback: str | None, state: Any) -> str: - # Imported lazily: the explorer stack is heavy and recursive (it spawns an LLM - # sub-agent), which is also why it must never live behind the action server. - from artemis.tools.explorer_tool import _run_explorer_logic + async def _recall_history(self, args: dict[str, Any]) -> tuple[str, list[dict] | None]: + """Deterministic cold-history lookup (same tool the Pro operator binds). + + Returns the text answer and, when screenshots were requested, the + multimodal content blocks the tool produced. + """ + from artemis.tools.history_recall import RecallHistoryArgs, recall_history + + accepted = {k: v for k, v in args.items() if k in RecallHistoryArgs.model_fields} + try: + result = await recall_history.execute(ctx=self.ctx, **accepted) + except Exception as e: + logger.error(f"Error running recall_history: {e}") + return f"recall_history failed: {e}", None + if isinstance(result, list): + texts = [ + b.get("text", "") for b in result if isinstance(b, dict) and b.get("type") == "text" + ] + return "\n".join(t for t in texts if t) or "Recalled history.", result + return str(result), None + + async def _video_analyzer(self, time_description: str, purpose: str) -> tuple[str, bool]: + """Runs the video-analyzing subagent over the session recording. + + Same subagent the Pro operator's ``video_analyzer`` LangChain tool + delegates to; imported lazily because the video stack is heavy. + """ + from artemis.agents.video_analyzer.video_analyzer import VideoAnalyzer try: - active_version = resolve_explorer_version( - self.ctx, - explicit_version=None, - agent_or_profile_name="validator", - ) - result = await _run_explorer_logic( - self.ctx, - state, - query, - context_feedback or "", - version=active_version, - ) - if isinstance(result, list): - texts = [ - item["text"] - for item in result - if isinstance(item, dict) and item.get("type") == "text" - ] - return "\n".join(texts) if texts else str(result) - return str(result) + outcome, status = await VideoAnalyzer(self.ctx).run(time_description, purpose) except Exception as e: - return f"Error executing ask_explorer: {e}" + logger.error(f"Error running video analyzer: {e}") + return f"Error running video analyzer: {e}", False + if status in ("failed", "error"): + return f"Video analysis failed: {outcome}", False + return outcome, True + + async def _ask_explorer( + self, query: str, context_feedback: str | None, state: Any + ) -> tuple[str, bool]: + """Runs the Explorer pipeline for this executor's agent; returns ``(text, ok)``. + + ``ok`` is False only when the Explorer run itself failed: a clean + "not found" is a successful answer the agent has to reason about. + """ + # Imported lazily: the explorer stack is heavy and recursive (it spawns an LLM + # sub-agent), which is also why it must never live behind the action server. + from artemis.tools.explorer_tool import locate, register_candidates, render_text + + try: + outcome = await locate( + self.ctx, state, query, context_feedback or "", agent_name=self.agent_name + ) + registered = register_candidates(self.ctx, state, outcome) + return render_text(query, outcome, registered), not outcome.error + except Exception as e: + return f"Error executing ask_explorer: {e}", False diff --git a/artemis/mcp/action_manifest.py b/artemis/mcp/action_manifest.py index 73885ff..ef5bd27 100644 --- a/artemis/mcp/action_manifest.py +++ b/artemis/mcp/action_manifest.py @@ -23,8 +23,10 @@ Two rules govern the split between required and optional: 1. A tool is REQUIRED only when a prompt depends on it *structurally*, i.e. the prompt cannot be assembled around its absence. Today that is ``click_sequence`` alone: the - two ``*_disappeared_analyzer.md`` prompts are built around "atomic chained execution - to defeat turn latency", which *is* ``click_sequence``. + Flash runner prompt's "Time-Sensitive Tasks" teaching is built around "atomic + chained execution to defeat turn latency", which *is* ``click_sequence``. (The Pro + Operator needs no such tool: a multi-action turn is executed by the Validator as an + unvetted fast-action burst.) 2. Everything else is OPTIONAL, because every other prompt reference lives in a tool enumeration slot or a self-contained instruction block that the assembly layer (``artemis/agents/prompt_assembly.py``) renders conditionally. An optional tool that @@ -124,7 +126,6 @@ BACKEND_INDEPENDENT_TOOLS: frozenset[str] = frozenset( "analyze_task_output", "recall_history", "report_task_status", - "report_failure_analysis", } ) @@ -139,7 +140,6 @@ AGENT_ROLES: frozenset[str] = frozenset( { "operator", "flash", - "failure_analyzer", "validator", } ) diff --git a/artemis/mcp/action_session.py b/artemis/mcp/action_session.py index 86009e0..4666d43 100644 --- a/artemis/mcp/action_session.py +++ b/artemis/mcp/action_session.py @@ -43,7 +43,7 @@ instead of leaving the rest of the run to fail on a corpse. The single owner loop also serializes calls, which enforces ARTEMIS's single-device policy at the one choke point all agents share -- in particular it -stops a nested Validator -> FailureAnalyzer flow from interleaving two device +stops nested device flows (e.g. Validator and a sub-agent) from interleaving two device actions. """ @@ -109,9 +109,7 @@ class ActionSession: # The transport is dead; no future call on this session can # succeed. Exit so `started` flips False and the next # get_action_session() builds a fresh session. - logger.error( - "Action session transport failed (%r); retiring this session.", e - ) + logger.error("Action session transport failed (%r); retiring this session.", e) return else: if not fut.cancelled(): @@ -172,9 +170,7 @@ class ActionSession: result = await self.call_raw(name, args) return ActionResult.model_validate(result.structuredContent) - async def observe( - self, settle_ms: int = 400, include_image: bool = False - ) -> ObserveResult: + async def observe(self, settle_ms: int = 400, include_image: bool = False) -> ObserveResult: """Captures and indexes the current screen via the internal observe tool.""" result = await self.call_raw( "observe_screen", {"settle_ms": settle_ms, "include_image": include_image} diff --git a/artemis/mcp/action_specs.py b/artemis/mcp/action_specs.py index 784eae1..1dbc8f4 100644 --- a/artemis/mcp/action_specs.py +++ b/artemis/mcp/action_specs.py @@ -15,7 +15,7 @@ """The canonical device-action manifest: every dialect of every action, side by side. Historically each device action was declared independently at three model-facing -sites -- the Operator's inline LangChain shells, the Validator/Flash/FailureAnalyzer +sites -- the Operator's inline LangChain shells, the Validator/Flash ``ToolDeclaration`` constants, and the action MCP server's function signatures -- and the declarations drifted apart (``click.target`` took an element index in one place and only coordinates in another). This module is now the single place all three are @@ -30,7 +30,7 @@ and declared**, not accidental: coordinates. The shells are declaration-only ("Action Recorded"): the Operator's translate step lowers them into structured decisions. * ``declaration`` -- the JSON ``ToolDeclaration`` bound by the Validator's - FailureAnalyzer and the FlashRunner. These agents act on coordinates directly (the + FlashRunner. These agents act on coordinates directly (the element list they see carries coordinates), so target parameters are coordinate pairs and the wording teaches the act-then-observe loop ("The screen after X will be returned automatically"). @@ -103,7 +103,7 @@ class OperatorDialect: @dataclass(frozen=True) class DeclarationDialect: - """The Validator/Flash/FailureAnalyzer JSON declaration, wording preserved.""" + """The Validator/Flash JSON declaration, wording preserved.""" description: str parameters: dict[str, Any] @@ -351,8 +351,8 @@ _SPECS: tuple[ActionSpec, ...] = ( bind=_wire_click_sequence, ), differences=( - "Declared to Flash and the disappeared-target FailureAnalyzer strategies" - " only; sequence entries may be element indices at the declaration layer" + "Declared to Flash only (the Pro Operator chains actions as a fast-action" + " burst instead); sequence entries may be element indices at the declaration layer" " (resolved client-side), while the wire takes coordinate pairs only." ), ), @@ -557,9 +557,8 @@ _SPECS: tuple[ActionSpec, ...] = ( ParamSpec( "duration", int | None, - "Optional swipe/drag duration in milliseconds (default 800). For drag-and-drop," - " list reordering, or sliding/adjusting sliders (e.g., volume, brightness, SeekBars)," - " set duration >= 1000 (e.g. 1500). If omitted for directional swipe, duration is computed automatically.", + "Optional swipe/drag duration in milliseconds (default 800; computed" + " automatically for directional swipes).", required=False, default=None, ), @@ -651,9 +650,8 @@ _SPECS: tuple[ActionSpec, ...] = ( name="press_key", operator=OperatorDialect( description=( - "[ACTION] Press a physical or virtual system button (e.g.\n" - "\n" - "ENTER, BACK, HOME, APP_SWITCH)." + "[ACTION] Press a physical or virtual system button (e.g. ENTER, BACK," + " HOME, APP_SWITCH)." ), params=( ParamSpec( diff --git a/artemis/mcp/actuators/adb.py b/artemis/mcp/actuators/adb.py index 096e7f9..b1ad3d7 100644 --- a/artemis/mcp/actuators/adb.py +++ b/artemis/mcp/actuators/adb.py @@ -158,16 +158,12 @@ class AdbActuator: # --- Optional actions ------------------------------------------------------------ - async def click( - self, nx: int, ny: int, times: int = 1, delay_ms: int = 100 - ) -> ActionResult: + async def click(self, nx: int, ny: int, times: int = 1, delay_ms: int = 100) -> ActionResult: x, y = self._to_px(nx, ny) result = await self.controller.tap_at(x, y, times=times, delay_ms=delay_ms) err = getattr(result, "error", None) if result else None if err is not None: - return ActionResult.failure( - "click", f"Error executing click: {err}", detail=str(err) - ) + return ActionResult.failure("click", f"Error executing click: {err}", detail=str(err)) return ActionResult.success( "click", f"Clicked at [{nx}, {ny}] (normalized) successfully.", @@ -283,9 +279,7 @@ class AdbActuator: detail=str(err), ) if not res: - return ActionResult.failure( - "press_key", f"Error executing key press '{key}'." - ) + return ActionResult.failure("press_key", f"Error executing key press '{key}'.") return ActionResult.success("press_key", f"Executed key press '{key}'.") @@ -328,9 +322,7 @@ class AdbActuator: res_term = self.controller.terminate_app(target_pkg) if inspect.iscoroutine(res_term): await res_term - return ActionResult.success( - "manage_app", f"Terminated app '{app_name}' successfully." - ) + return ActionResult.success("manage_app", f"Terminated app '{app_name}' successfully.") return ActionResult.failure( "manage_app", f"Invalid manage_app action: {action}", diff --git a/artemis/mcp/adb_server.py b/artemis/mcp/adb_server.py index 6b20604..507b103 100644 --- a/artemis/mcp/adb_server.py +++ b/artemis/mcp/adb_server.py @@ -29,12 +29,15 @@ if _REPO_ROOT not in sys.path: from adbutils import AdbClient from mcp.server.fastmcp import Context, FastMCP +logger = logging.getLogger(__name__) + try: from mcp.server.fastmcp.server import Settings as FastMCPSettings FastMCPSettings.model_rebuild() -except Exception: - pass +except Exception as exc: # pylint: disable=broad-exception-caught + # Best-effort compatibility shim for FastMCP/pydantic version drift. + logger.debug("FastMCP Settings model_rebuild skipped: %s", exc, exc_info=True) from artemis.clients.ui_automator_client import UIAutomatorClient from artemis.context import ArtemisContext, DeviceContext, DevicePlatform @@ -42,8 +45,6 @@ from artemis.controllers.unified_controller import UnifiedMobileController from artemis.platform import platform from artemis.utils.app_launch_utils import launch_app_with_retries -logger = logging.getLogger(__name__) - def configure_stdio_mode() -> None: """Applies process-wide settings required when serving MCP over stdio. @@ -93,7 +94,9 @@ _CONTROLLERS: dict[str, Any] = {} def _get_controller(device_serial: str | None = None): """Lazy-load device controller on-demand, caching per device serial.""" global _GLOBAL_CONTROLLER, _CONTROLLERS - target_serial = device_serial or os.environ.get("ARTEMIS_DEVICE_ID") or os.environ.get("ADB_DEVICE_SERIAL") + target_serial = ( + device_serial or os.environ.get("ARTEMIS_DEVICE_ID") or os.environ.get("ADB_DEVICE_SERIAL") + ) if target_serial and target_serial in _CONTROLLERS: return _CONTROLLERS[target_serial] if not target_serial and _GLOBAL_CONTROLLER is not None: diff --git a/artemis/mcp/observation.py b/artemis/mcp/observation.py index 28954b1..42fffa6 100644 --- a/artemis/mcp/observation.py +++ b/artemis/mcp/observation.py @@ -16,7 +16,7 @@ This is the act-then-observe half that used to be welded into every executor action (``capture_screenshot_and_parse_ui``). Split out so that actuation and observation are -separate calls: the Flash/FailureAnalyzer adapter observes after each action, while the +separate calls: the Flash adapter observes after each action, while the Validator -- which runs its own UI-change polling -- simply never calls it. """ diff --git a/artemis/mcp/xml_search_server.py b/artemis/mcp/xml_search_server.py index 6816b7a..8fc9075 100644 --- a/artemis/mcp/xml_search_server.py +++ b/artemis/mcp/xml_search_server.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import logging import os from pathlib import Path import re @@ -25,12 +26,15 @@ if _REPO_ROOT not in sys.path: # pylint: disable=wrong-import-position from mcp.server.fastmcp import FastMCP +logger = logging.getLogger(__name__) + try: from mcp.server.fastmcp.server import Settings as FastMCPSettings FastMCPSettings.model_rebuild() -except Exception: - pass +except Exception as exc: # pylint: disable=broad-exception-caught + # Best-effort compatibility shim for FastMCP/pydantic version drift. + logger.debug("FastMCP Settings model_rebuild skipped: %s", exc, exc_info=True) from artemis.data_engine.storage import StorageManager diff --git a/artemis/memory/__init__.py b/artemis/memory/__init__.py index 1277bd3..5b005df 100644 --- a/artemis/memory/__init__.py +++ b/artemis/memory/__init__.py @@ -28,6 +28,9 @@ from artemis.memory.context_policy import ( ) from artemis.memory.step_memory import StepLens, StepMemoryService from artemis.memory.transcript import TranscriptLedger, format_session_offset +from artemis.utils.logger import get_logger + +logger = get_logger(__name__) def ensure_step_memory(ctx): @@ -56,13 +59,16 @@ def ensure_step_memory(ctx): "max_concurrency": cfg.memory.runtime.max_concurrency, "flush_timeout_s": cfg.memory.runtime.flush_timeout_s, } - except Exception: - pass + except Exception as exc: + logger.debug( + f"Agent config unavailable; using step-memory service defaults: {exc}", + exc_info=True, + ) service = VisualStepSummarizer(ctx, model_name=model_name, **kwargs) try: ctx.step_memory = service - except Exception: + except (AttributeError, TypeError, ValueError): pass return service diff --git a/artemis/memory/chunking.py b/artemis/memory/chunking.py index 08c4098..b60c1a3 100644 --- a/artemis/memory/chunking.py +++ b/artemis/memory/chunking.py @@ -27,10 +27,10 @@ width, never the step count. Every chunk renders as three bands: gap forces regeneration, bounded retries degrade to pending). - **③ Per-step action ledger** (mechanical, zero-distortion, :func:`build_action_ledger`): one line per step — step number, ``T+mm:ss`` - session offset, ``format_action_clean`` semantic action, controller/ - validator result phrase; Failure-Analyzer recovery actions as indented - sub-lines; user-injected instructions verbatim on their own never-evicted - lines. + session offset, ``format_actions_clean`` semantic action (a fast-action + burst lists every member), controller/validator result phrase (an execution + incident renders as the ``Error:`` phrase); user-injected instructions + verbatim on their own never-evicted lines. Triggers (:class:`HistoryChunkManager`): milestone switch (sole fact source: the *stamped* ``subgoal_hash`` changing between consecutive recorded steps; @@ -83,9 +83,7 @@ logger = get_logger(__name__) #: Recall guidance line rendered under an extreme-layer period paragraph #: (an era whose per-step ledger overflowed to recall-only; §3.3). -RECALL_GUIDANCE_TEMPLATE = ( - " (Step-level ledger via recall_history for steps {start}–{end})" -) +RECALL_GUIDANCE_TEMPLATE = " (Step-level ledger via recall_history for steps {start}–{end})" CHUNK_PENDING_NOTE = ( "①/② capsule pending (background generation); the mechanical" @@ -121,37 +119,14 @@ def _result_phrase(result: Any) -> str: def _action_phrase(step: dict) -> str: - """Semantic action phrase for one step (``format_action_clean``).""" - from artemis.utils.task_tree import format_action_clean + """Semantic action phrase for one step (``format_actions_clean``). - action = step.get("action_taken") - text = format_action_clean(action) - if isinstance(action, list) and len(action) > 1: - text += f" (+{len(action) - 1} more actions)" - return text + A fast-action burst (2+ actions in one turn) lists every member so the + ledger stays zero-distortion. + """ + from artemis.utils.task_tree import format_actions_clean - -def _fa_recovery_lines(step: dict) -> list[str]: - """Failure-Analyzer device actions of a step, rendered mechanically.""" - from artemis.utils.task_tree import format_tool_call_clean - - lines: list[str] = [] - for event in step.get("interleaved_events") or []: - name = event.get("name") or "" - if ( - event.get("type") == "tool_call" - and name.startswith("_exec_") - and name != "report_failure_analysis" - ): - try: - rendered = format_tool_call_clean( - name, event.get("args") or {}, event.get("result") - ) - except Exception: - rendered = None - if rendered: - lines.append(rendered) - return lines + return format_actions_clean(step.get("action_taken")) def step_offset_label(step: dict, session_start: float | None) -> str: @@ -190,9 +165,9 @@ def build_action_ledger( if minimal: lines.append(f"- Step {number} ({offset}): {action}") else: - lines.append(f"- Step {number} ({offset}): {action} -> {_result_phrase(step.get('last_execution_result'))}") - for recovery in _fa_recovery_lines(step): - lines.append(f" FA: {recovery}") + lines.append( + f"- Step {number} ({offset}): {action} -> {_result_phrase(step.get('last_execution_result'))}" + ) user_line = injected_instruction_line(step) if user_line: lines.append(user_line) @@ -285,7 +260,7 @@ class StepCapsuleLens(StepLens): fallback_model_name: str | None = None, fallback_llm: Any | None = None, ): - self._model_name = model_name or "gemini-3.7-flash" + self._model_name = model_name or "gemini-3.8-flash" self._llm = llm self._ctx = ctx # Availability hardening: `chunking.model` is a dedicated model with no @@ -352,15 +327,9 @@ class StepCapsuleLens(StepLens): if step.get("thinking_excerpt"): part.append(f"Reasoning excerpt: {step['thinking_excerpt']}") for write in step.get("note_writes", []): - part.append( - f"Note written ({write['tool']} -> {write['key']}): {write['gist']}" - ) + part.append(f"Note written ({write['tool']} -> {write['key']}): {write['gist']}") if step.get("injected_instruction"): - part.append( - f'User injected instruction: "{step["injected_instruction"]}"' - ) - for fa in step.get("fa_actions", []): - part.append(f"Failure-Analyzer recovery: {fa}") + part.append(f'User injected instruction: "{step["injected_instruction"]}"') blocks.append("\n".join(part)) return [ @@ -430,8 +399,8 @@ class StepCapsuleLens(StepLens): source=f"lens:step_capsule:{model_label}", update_last_prompt=False, ) - except Exception: - pass + except Exception as exc: + logger.debug(f"Capsule LLM usage metering skipped: {exc}", exc_info=True) return response async def render(self, key: JobKey, payload: dict[str, Any]) -> str | None: @@ -577,9 +546,7 @@ def render_chunk_block(chunk: ChunkState) -> str: parts.append(f" What this segment was doing: {b1.get('doing', '')}") parts.append(f" What was actually done: {b1.get('did', '')}") parts.append(f" Effects / left behind: {b1.get('effect', '')}") - parts.append( - f" Entry: {b1.get('entry_state', '')} Exit: {b1.get('exit_state', '')}" - ) + parts.append(f" Entry: {b1.get('entry_state', '')} Exit: {b1.get('exit_state', '')}") parts.append(f" Verified: {'; '.join(b1.get('verified_facts') or []) or '-'}") parts.append(f" Unresolved: {'; '.join(b1.get('unresolved') or []) or '-'}") parts.append(f" Failed paths: {'; '.join(b1.get('failed_paths') or []) or '-'}") @@ -627,10 +594,7 @@ def render_era_period_paragraph(era: EraState) -> str: """ ready = [c for c in era.chunks if c.band1] if not ready: - labels = [ - f"{c.milestone_label or 'segment'} ({c.step_range_label})" - for c in era.chunks - ] + labels = [f"{c.milestone_label or 'segment'} ({c.step_range_label})" for c in era.chunks] return "Milestones: " + "; ".join(labels) + "." sentences: list[str] = [] @@ -643,7 +607,9 @@ def render_era_period_paragraph(era: EraState) -> str: "", ) if doing: - sentences.append(doing if doing.endswith((".", "!", "?", "。", "!", "?")) else doing + ".") + sentences.append( + doing if doing.endswith((".", "!", "?", "。", "!", "?")) else doing + "." + ) did_parts: list[str] = [] for chunk in era.chunks: @@ -685,9 +651,7 @@ def render_era_block(era: EraState) -> str: f" | {era.chunks[0].start_offset} → {era.chunks[-1].end_offset}]" f" {render_era_period_paragraph(era)}" ), - RECALL_GUIDANCE_TEMPLATE.format( - start=era.start_step_number, end=era.end_step_number - ), + RECALL_GUIDANCE_TEMPLATE.format(start=era.start_step_number, end=era.end_step_number), ] # Never-evict: user-injected instruction lines survive even recall-only. for chunk in era.chunks: @@ -805,7 +769,7 @@ class HistoryChunkManager: cc = chunking_config self._max_steps = int(getattr(cc, "max_steps", 12) or 12) self._target_source_tokens = int(getattr(cc, "target_source_tokens", 2000) or 2000) - self._model_name = getattr(cc, "model", None) or "gemini-3.7-flash" + self._model_name = getattr(cc, "model", None) or "gemini-3.8-flash" self._max_chunks = int(getattr(cc, "max_chunks", 8) or 8) # Independent era cap (M5): None follows max_chunks (pre-M5 behavior). self._max_eras = int(getattr(cc, "max_eras", None) or self._max_chunks) @@ -847,8 +811,11 @@ class HistoryChunkManager: "max_concurrency": runtime.max_concurrency, "flush_timeout_s": runtime.flush_timeout_s, } - except Exception: - pass + except Exception as exc: + logger.debug( + f"Memory runtime config unavailable; using capsule service defaults: {exc}", + exc_info=True, + ) lens = StepCapsuleLens( self._model_name, ctx=ctx, @@ -874,8 +841,8 @@ class HistoryChunkManager: model = getattr(fallback, "model", None) if model and provider in ("google", "gemini") and model != self._model_name: return str(model) - except Exception: - pass + except Exception as exc: + logger.debug(f"Capsule fallback model resolution skipped: {exc}", exc_info=True) return None @property @@ -927,9 +894,7 @@ class HistoryChunkManager: self._boundary_hint_pending = False self._last_hash = stamped - def annotate_from_checkpoint( - self, checkpoint_id: str, verdicts: list[dict[str, Any]] - ) -> bool: + def annotate_from_checkpoint(self, checkpoint_id: str, verdicts: list[dict[str, Any]]) -> bool: """Post-hoc annotation: a harvested checkpoint verdict landed after its segment was chunked. The matching chunk gains an annotation and a version bump (DB immediately; the frozen text re-renders only at the @@ -954,8 +919,8 @@ class HistoryChunkManager: base_dir = getattr(self._engine, "base_dir", None) if base_dir: aliases |= set(get_all_subgoal_aliases(checkpoint_id, base_dir)) - except Exception: - pass + except Exception as exc: + logger.debug(f"Subgoal alias lookup for {checkpoint_id} skipped: {exc}", exc_info=True) return aliases def _all_chunks(self) -> list[ChunkState]: @@ -1148,8 +1113,7 @@ class HistoryChunkManager: def _slices(self, seg_turns: list[dict]) -> list[list[dict]]: return [ - seg_turns[i : i + self._max_steps] - for i in range(0, len(seg_turns), self._max_steps) + seg_turns[i : i + self._max_steps] for i in range(0, len(seg_turns), self._max_steps) ] def _context_base_tokens(self) -> int | None: @@ -1191,7 +1155,14 @@ class HistoryChunkManager: seg_hash: str | None, steps_by_id: dict[str, dict], ) -> ChunkState | None: - step_keys = [str(t.get("step_key")) for t in slice_turns if t.get("step_key")] + # A turn carries every step id it recorded (``step_keys``, Flash + # multi-action turns); older ledgers only expose ``step_key``. + step_keys: list[str] = [] + for turn in slice_turns: + keys = turn.get("step_keys") or ([turn.get("step_key")] if turn.get("step_key") else []) + for key in keys: + if key and str(key) not in step_keys: + step_keys.append(str(key)) steps = [steps_by_id[k] for k in step_keys if k in steps_by_id] steps.sort(key=lambda s: s.get("step_number") or 0) if not steps: @@ -1217,9 +1188,7 @@ class HistoryChunkManager: end_offset=step_offset_label(end_step, session_start), band3=build_action_ledger(steps, session_start), minimal_index=build_action_ledger(steps, session_start, minimal=True), - user_lines=[ - line for line in (injected_instruction_line(s) for s in steps) if line - ], + user_lines=[line for line in (injected_instruction_line(s) for s in steps) if line], ) # Ready gating: the caller queues the chunk as awaiting — it only # enters self._chunks (and the frozen region) once its capsule is @@ -1240,13 +1209,11 @@ class HistoryChunkManager: "action": _action_phrase(step), "outcome": _result_phrase(step.get("last_execution_result")), "visual_summary": step.get("summary"), - "thinking_excerpt": (step.get("operator_raw_thinking") or "")[:1200] - or None, + "thinking_excerpt": (step.get("operator_raw_thinking") or "")[:1200] or None, "note_writes": extract_note_writes(step), "injected_instruction": (step.get("extra_metadata") or {}).get( "injected_instruction" ), - "fa_actions": _fa_recovery_lines(step), } ) payload = { @@ -1363,9 +1330,7 @@ class HistoryChunkManager: return [HumanMessage(content=[{"type": "text", "text": text}])] blocks: list[BaseMessage] = [] for era in self._eras: - blocks.append( - HumanMessage(content=[{"type": "text", "text": render_era_block(era)}]) - ) + blocks.append(HumanMessage(content=[{"type": "text", "text": render_era_block(era)}])) for chunk in self._chunks: blocks.append( HumanMessage(content=[{"type": "text", "text": render_chunk_block(chunk)}]) @@ -1397,8 +1362,8 @@ class HistoryChunkManager: """ try: await self._capsule_service.flush(timeout_seconds) - except Exception: - pass + except Exception as exc: + logger.debug(f"Capsule service flush failed; skipped: {exc}", exc_info=True) try: self._harvest_capsules() except Exception as e: diff --git a/artemis/memory/context_policy.py b/artemis/memory/context_policy.py index d6fafe9..f187298 100644 --- a/artemis/memory/context_policy.py +++ b/artemis/memory/context_policy.py @@ -70,12 +70,11 @@ class ContextPolicy: uncompressed: bool = False last_n_detailed: int = 1 all_detailed: bool = False - for_failure_analyzer: bool = False whitelist: bool = False chunk_view: str | None = "digest" # "full" | "digest" | None (never chunks) -#: The eight production call sites, verbatim (kwargs recorded 2026-09-01). +#: Default compiled-history settings for each agent. CONTEXT_POLICIES: dict[str, ContextPolicy] = { # operator.py:996 — last_n_detailed comes from the node constructor. "operator": ContextPolicy( @@ -90,14 +89,6 @@ CONTEXT_POLICIES: dict[str, ContextPolicy] = { last_n_detailed=1, chunk_view=None, ), - # failure_analyzer.py:361 - "failure_analyzer": ContextPolicy( - strategy="strict_milestone", - strict_milestone_pruning=True, - recent_window_size=3, - last_n_detailed=1, - for_failure_analyzer=True, - ), # planner.py:133 — caller passes an empty plan and hash "default". "planner": ContextPolicy( strategy="strict_milestone", @@ -195,14 +186,8 @@ def load_chunk_blocks(engine: Any, view: str | None) -> list[dict] | None: rendered = getattr(row, "rendered_text", None) if not isinstance(start, int) or not isinstance(end, int) or not rendered: continue - text = ( - rendered - if view == "full" - else _chunk_digest_text(rendered, start, end) - ) - blocks.append( - {"start_step_number": start, "end_step_number": end, "text": text} - ) + text = rendered if view == "full" else _chunk_digest_text(rendered, start, end) + blocks.append({"start_step_number": start, "end_step_number": end, "text": text}) return blocks or None @@ -230,13 +215,10 @@ def build_history_for( policy = resolve_policy(agent) kwargs: dict[str, Any] = { "min_summaries": len(steps) if policy.uncompressed else policy.min_summaries, - "last_n_detailed": ( - policy.last_n_detailed if last_n_detailed is None else last_n_detailed - ), + "last_n_detailed": (policy.last_n_detailed if last_n_detailed is None else last_n_detailed), "all_detailed": policy.all_detailed, "strict_milestone_pruning": policy.strict_milestone_pruning, "recent_window_size": policy.recent_window_size, - "for_failure_analyzer": policy.for_failure_analyzer, } if policy.whitelist: kwargs["keep_subgoal_hashes"] = keep_subgoal_hashes diff --git a/artemis/memory/transcript.py b/artemis/memory/transcript.py index 33f7eae..0e2332c 100644 --- a/artemis/memory/transcript.py +++ b/artemis/memory/transcript.py @@ -33,7 +33,15 @@ Implements the four-region discipline of the history-module redesign §3.2: All timestamps inside the transcript use the session-start offset ``T+mm:ss`` (byte-stable once frozen); "ago" wording is reserved for the auxiliary agents' -per-call compiled views. +per-call compiled views. When the caller supplies the DataEngine +``session_start`` epoch, the ledger clock is anchored to it so the tail +offsets, the chunk ledger lines and the video analyzer's action timeline all +share one origin. + +Profiles: the Pro operator (one committed turn per graph step) and the Flash +runner (one committed turn per reactive turn; a multi-action turn registers +every recorded step id via ``extra_step_keys`` so the chunk ledger lists each +step) build their prompts from the same ledger. """ import json @@ -84,9 +92,18 @@ class TranscriptLedger: pending_grace_steps: int = 3, xml_scrub_depth: int = 1, clock: Callable[[], float] | None = None, + session_start: float | None = None, ): - self._clock = clock or time.monotonic - self._session_start = self._clock() + # ``session_start`` is an epoch-seconds origin (the DataEngine's + # ``session_start_time``); with it the ledger reads the wall clock so + # every ``T+mm:ss`` label is relative to the recorded session start. + # Without it the ledger keeps its own monotonic origin. + if session_start is not None: + self._clock = clock or time.time + self._session_start = float(session_start) + else: + self._clock = clock or time.monotonic + self._session_start = self._clock() self._static: list[BaseMessage] = [] self._restored: list[BaseMessage] = [] @@ -163,12 +180,8 @@ class TranscriptLedger: if self._restored: raise RuntimeError("TranscriptLedger restored history is already set.") if self._active or self._staged or self._turn_count: - raise RuntimeError( - "Restored history can only seed an empty ledger (cold start)." - ) - self._restored = [ - HumanMessage(content=[{"type": "text", "text": text}]) - ] + raise RuntimeError("Restored history can only seed an empty ledger (cold start).") + self._restored = [HumanMessage(content=[{"type": "text", "text": text}])] # ------------------------------------------------------------------ # A region (append-only turn commits) @@ -205,6 +218,7 @@ class TranscriptLedger: *, step_key: str | None = None, validator_result: Any | None = None, + extra_step_keys: tuple[str, ...] | list[str] = (), ) -> None: """Move the staged turn into the active region. @@ -215,6 +229,10 @@ class TranscriptLedger: validator_result: The turn's validator report (``None`` when the turn executed no terminal action); appended as a frozen result message carrying the ``T+mm:ss`` session offset. + extra_step_keys: Further DataEngine step ids recorded during the + same turn (a Flash turn that executed several actions records + one step per action). They join ``step_key`` in the turn's + ``step_keys`` so chunk compression lists every step. """ if self._staged is None: return @@ -234,9 +252,14 @@ class TranscriptLedger: if result_message is not None: self._active.append(result_message) + step_keys: list[str] = [] + for key in (step_key, *extra_step_keys): + if key is not None and str(key) not in step_keys: + step_keys.append(str(key)) self._turns.append( { "step_key": str(step_key) if step_key is not None else None, + "step_keys": step_keys, "start": span_start, "end": len(self._active), } @@ -279,9 +302,7 @@ class TranscriptLedger: content=[ { "type": "text", - "text": ( - f"{EXECUTION_RESULT_MARKER} ({self.elapsed_label()}) ---\n{rendered}" - ), + "text": (f"{EXECUTION_RESULT_MARKER} ({self.elapsed_label()}) ---\n{rendered}"), } ] ) diff --git a/artemis/platform/darwin.py b/artemis/platform/darwin.py index f4e870b..219aa5b 100644 --- a/artemis/platform/darwin.py +++ b/artemis/platform/darwin.py @@ -112,9 +112,7 @@ class DarwinPlatformProcess(IPlatformProcess): if p.pid != current_pid and getattr(p, "ppid", lambda: None)() != current_pid ] direct_children = [ - p - for p in children + [parent] - if getattr(p, "ppid", lambda: None)() == current_pid + p for p in children + [parent] if getattr(p, "ppid", lambda: None)() == current_pid ] if external_procs: diff --git a/artemis/platform/linux.py b/artemis/platform/linux.py index e29e7b3..d423f80 100644 --- a/artemis/platform/linux.py +++ b/artemis/platform/linux.py @@ -127,9 +127,7 @@ class LinuxPlatformProcess(IPlatformProcess): if p.pid != current_pid and getattr(p, "ppid", lambda: None)() != current_pid ] direct_children = [ - p - for p in children + [parent] - if getattr(p, "ppid", lambda: None)() == current_pid + p for p in children + [parent] if getattr(p, "ppid", lambda: None)() == current_pid ] if external_procs: diff --git a/artemis/resources/__init__.py b/artemis/resources/__init__.py new file mode 100644 index 0000000..e883b1f --- /dev/null +++ b/artemis/resources/__init__.py @@ -0,0 +1,46 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Access to immutable defaults and web assets shipped with Artemis. + +Python wheels are installed as ordinary directories by pip, so callers that +need to hand a path to another library (for example FastAPI's ``FileResponse``) +can safely use the paths returned here. Source checkouts continue to prefer +their editable ``config`` and ``apps/showcase_ui/dist`` trees; these resources +are the installation fallback. +""" + +from importlib.resources import files +from pathlib import Path + + +def _resource_path(*parts: str) -> Path: + """Return a filesystem path inside the installed resource package.""" + resource = files(__name__).joinpath(*parts) + return Path(str(resource)) + + +def get_bundled_config_path(filename: str) -> Path | None: + """Return a bundled configuration template when it exists.""" + candidate = _resource_path("config", filename) + return candidate if candidate.is_file() else None + + +def get_bundled_showcase_dist() -> Path | None: + """Return the bundled Angular browser build when it is complete.""" + candidate = _resource_path("showcase_ui") + return candidate if (candidate / "index.html").is_file() else None + + +__all__ = ["get_bundled_config_path", "get_bundled_showcase_dist"] diff --git a/artemis/resources/config/artemis.jsonc b/artemis/resources/config/artemis.jsonc new file mode 100644 index 0000000..11359f6 --- /dev/null +++ b/artemis/resources/config/artemis.jsonc @@ -0,0 +1,268 @@ +{ + // ============================================================================== + // ☕ ARTEMIS Autonomous Agent & Framework Configuration + // ============================================================================== + + // ------------------------------------------------------------------------------ + // 1. Global Default Model (Applied to all sub-agents unless overridden below) + // ------------------------------------------------------------------------------ + "default": { + "provider": "google", + "model": "gemini-3.8-flash", + "thinking_level": "medium", + "fallback": { + "provider": "google", + "model": "gemini-3.7-flash", + "thinking_level": "medium" + } + }, + + // ------------------------------------------------------------------------------ + // 2. Ready-to-Use Presets (Switch easily in code or CLI) + // ------------------------------------------------------------------------------ + "presets": { + "gemini-flagship": { + "provider": "google", + "model": "gemini-3.8-flash", + "fallback": { "provider": "google", "model": "gemini-3.7-flash" } + }, + "gemini-flash": { + "provider": "google", + "model": "gemini-2.5-flash", + "fallback": { "provider": "google", "model": "gemini-2.0-flash" } + }, + "openai-gpt4o": { + "provider": "openai", + "model": "gpt-4o", + "fallback": { "provider": "openai", "model": "gpt-4o-mini" } + }, + "cost-saving": { + "provider": "google", + "model": "gemini-3.5-flash-lite", + "fallback": { "provider": "google", "model": "gemini-3.1-flash-lite" } + }, + "local-ollama": { + "provider": "openai", + "model": "llama3.2-vision", + "fallback": { "provider": "openai", "model": "llama3.2-vision" } + } + }, + + // ------------------------------------------------------------------------------ + // 3. Agent & Sub-Agent Node Customizations (Optional - Inherits from 'default') + // ------------------------------------------------------------------------------ + "nodes": { + // 🧠 Planner: High-level goal decomposition and strategic step planning + "planner": { + "thinking_level": "high" + }, + + // 🎯 Operator: Screen perception, element interaction, and direct execution + "operator": { + "thinking_level": "medium", + "include_thoughts": true + }, + + // 🎬 Video Analyzer: Multimodal screen recording verification and temporal analysis + // NOTE: Dual-path engine automatically routes: + // - Native Gemini Files API for Gemini models with Google API keys (long video file stream) + // - Universal Multi-Model Engine for Claude 3.5/3.7, GPT-4o, OpenRouter, Vertex AI (dynamic keyframe extraction + multimodal tool calling) + "video_analyzer": {}, + + // 👁️ Object Detector & Explorer (Flash Mode): Visual coordinates grounding and bounding boxes + // CRITICAL REQUIREMENT: Standard LLMs (including standard Gemini Flash, GPT-4o, Claude 3.7) lack sub-pixel spatial coordinate fine-tuning. + // To achieve accurate [x, y] point and bounding box localization, this node MUST use specialized Gemini ER (Embodied Reasoning / Robotics) models: + // e.g., "gemini-robotics-er-2-preview". Non-ER models will fail spatial coordinate detection. + "object_detector": { + "model": "gemini-robotics-er-2-preview", + "fallback": { + "provider": "google", + "model": "gemini-robotics-er-2-preview" + } + }, + + // 🔍 Explorer: Deep element grounding and multi-candidate visual search + // NOTE: + // - "flash" version delegates directly to object_detector (requires Gemini ER model above). + // - "pro" / "ultra" versions run multi-turn ReAct loops (supports both Native Gemini and Universal Multi-Model LangChain engines). + "explorer": { + "thinking_level": "medium" + }, + + // 📋 Summarizer: Aggregates step history and generates structured outcome + "summarizer": {}, + + // 🔽 Hopper: Fast lightweight package name locator + "hopper": { + "model": "gemini-3.5-flash-lite", + "fallback": { + "provider": "google", + "model": "gemini-3.1-flash-lite" + } + }, + + // 📝 Outputter: Synthesizes execution trace into final output report + "outputter": {}, + + // 🪵 Log Analyzer: Error log diagnosis with search grounding + "log_analyzer": { + "enable_grounding": true + }, + + // 🩺 Diagnoser Expert: Failure root cause diagnosis with search grounding + "diagnoser_expert": { + "enable_grounding": true + }, + // 🏛️ Committee Council Members + "planner_avatar": {}, + "history_analyzer_expert": {}, + // 🔎 Checker: Plan-declared checkpoint verification and exit final review. + // Thoughts are streamed so the verifier's reasoning shows live in the timeline. + "checker": { + "thinking_level": "medium", + "include_thoughts": true + } + // 🎯 Planner Validation: Advisory review of milestone changes. + // Defaults to the same lightweight model as the pixel safety net + // (gemini-3.5-flash-lite, temperature 0); add "planner_validation": {...} here to override. + }, + + // ------------------------------------------------------------------------------ + // 4. Runtime Mode & Agent Capabilities Configuration (Dual-Engine: Flash vs. Pro) + // ------------------------------------------------------------------------------ + "agent": { + // ========================================================================== + // ⚡ 4.1 FLASH Profile Options (Reactive Single-Agent Lightweight Loop) + // ========================================================================== + // Best for: Simple, fast, deterministic tasks; runs unbounded by default. + // Execution: Observe-Think-Act single LLM reactive loop without graph overhead. + "flash": { + "max_turns": 0, // Maximum reactive turns before stopping (0 = unlimited; history is bounded by transcript compression, not by a turn cap) + "explorer_mode": "flash", // Explorer tier for the Flash runner: 'flash' | 'pro' | 'ultra' (see top-level "explorer") + // 🧠 Step Summarizer: Asynchronous visual context compressor + // Summarizes intermediate steps in background without blocking the runner, + // replacing images only after strictly objective, high-density summaries are ready. + "step_summarizer": { + "enabled": true, // Whether background step summarization is active + "model": "gemini-3.5-flash-lite", // Lightweight model for fast background inference + "prune_history_xml": true // Prune outdated heavy XML trees from historical steps + } + }, + // ========================================================================== + // 🚀 4.2 PRO Profile Options (Deep Multi-Agent Graph Closed-Loop) + // ========================================================================== + // Best for: Complex, exploratory, dynamic multi-branch tasks. + // Execution: LangGraph multi-node graph with a living task plan, a pre-execution safety net (single actions) / fast-action bursts (multi-action turns), + // execution-incident recovery handled by the Operator itself (no separate repair agent), and a read-only Checker for checkpoint verification + exit final review. + "pro": { + // 🧭 Explorer: Perception sub-agent under Pro profile ('flash', 'pro', or 'ultra') + "explorer": { + "mode": "flash" // Explorer tier for Pro nodes (Operator/Validator): 'flash' | 'pro' | 'ultra' + }, + // 🎯 Planner Validation: Async verification when task plan milestones change + "planner_validation": { + "enabled": true // On by default: every milestone text change is reviewed (advisory: hint + reason only, never rolled back) + }, + // 🏛️ Committee: Multi-Agent Council debate tool for Operator + "committee": { + "enabled": false, + "debate_rounds": 2 + }, + // 🔎 Checker: Plan-declared checkpoint verification (optimistic async) + exit final review + "checker": { + "enabled": true, // Master switch (compat alias): false disables both gates below + "midway_checks": false, // Plan-declared midway checkpoints (off in the factory layering) + "final_check": true, // Exit final review against the user's original goal (on by default) + "max_iterations": 20, + "final_check_max_attempts": 3, + "checkpoint_max_repairs": 2, + "max_concurrent_checkpoints": 3, + "checkpoint_timeout": 180.0, + "settlement_timeout": 120.0, + "assert_failure_policy": "continue", // 'continue' or 'halt' on assert failures + "device_probes": true // Checker's enumerated read-only device probes + }, + // 🎬 Video Analyzer & Screen Recording: Continuous screen recording via scrcpy and temporal analysis + "video_analyzer": { + "enabled": true, // Whether continuous video recording and video tools are active (default: auto-detected) + "enable_ledger": true + } + }, + // ========================================================================== + // 🌐 4.3 Shared Capabilities & Global Overrides + // ========================================================================== + // 🧠 Unified Step-Memory Runtime (history-module redesign v3, M0–M5) + // Transcript ledger + layered compression for the Pro operator. + "memory": { + // Background summarization runtime (visual/capsule lenses) + "runtime": { + "max_concurrency": 2, // 1→2 per 2026-09-01 baseline: serial queue pushed summary-ready P90 past the scrub window + "retry_limit": 3, + "flush_timeout_s": 30 + }, + "transcript": { + // ROLLBACK SWITCH: set false to restore the legacy per-turn 2-message + // prompt path byte-for-byte (L2/L3 chunk compression rides this same + // flag). Default on since M5 (2026-09-01) per the on-device A/B: + // turn latency −77% P50, cache-hit calls 43%→88%, output tokens −74%. + "enabled": true, + "context_budget_tokens": 80000, + "soft_ratio": 0.7, + "hard_ratio": 0.9, + "min_active_steps": 5, + "image_scrub_depth": 3, + "pending_grace_steps": 3, + "xml_scrub_depth": 1, + // Local historical-state hint (no model call); distance calibrated + // 2026-09-01 from 460 dHash-stamped on-device steps (same screen <=4, + // different screens >=7). + "similarity_hint": true, + "similarity_max_distance": 5 + }, + // Segment (L2) chunk compression: three-band chunks, era folding + "chunking": { + "max_steps": 12, + "target_source_tokens": 2000, + "model": "gemini-3.8-flash", + "max_chunks": 8 + // "max_eras": 8 // independent era cap; omitted = follows max_chunks + }, + // Deterministic cold-history recall tool (Pro operator) + "recall": { + "enabled": true, + "max_results": 5, + "max_text_tokens": 2000, + "max_image_steps": 1 + } + }, + // 🎬 Screen Recording & Video Analyzer Configuration + "video_analyzer": { + "enabled": true, // Whether continuous screen recording via scrcpy is active (default: auto-detected) + "enable_ledger": true + }, + // 📝 Outputter: Post-execution report synthesis and structured schema extraction + // Reviews action history, notes, and verifies goal achievement to synthesize final output. + "outputter": { + "enabled": true, // Whether Outputter is mounted to synthesize final outputs + "force_synthesis": false // Force Outputter synthesis even without explicit output schema + }, + // 🧭 Explorer tier: the perception sub-agent behind `ask_explorer`. + // Tiers are a user setting; calling agents never see which tier is active. + // flash - one-shot visual detection on the screenshot (fastest) + // pro - 3-turn reasoning loop: UI-tree search + coordinate audit + detection + // ultra - 8-turn loop adding zoom, OCR and pixel-level image processing (slowest) + // `flash.explorer_mode` and `pro.explorer.mode` above are the per-profile knobs; + // `caching` (Gemini explicit context caching) defaults per tier: off for pro, on for ultra. + "explorer": { + "default_version": "flash" // Fallback tier when no profile knob applies + // "caching": true, // Uncomment to force context caching for pro/ultra + }, + // Advanced: per-agent tier override, e.g. {"validator": "ultra"}. Takes + // precedence over the profile knobs; leave empty to let them decide. + "explorer_versions": {}, +// 🚫 Per-agent Tool Denylists + "denylisted_tools": { + "explorer": [] // Perception tools to withhold from the Explorer, e.g. "ask_image_processor" + } + } +} diff --git a/artemis/resources/showcase_ui/favicon.ico b/artemis/resources/showcase_ui/favicon.ico new file mode 100644 index 0000000..df9d973 Binary files /dev/null and b/artemis/resources/showcase_ui/favicon.ico differ diff --git a/artemis/resources/showcase_ui/index.html b/artemis/resources/showcase_ui/index.html new file mode 100644 index 0000000..1a249ea --- /dev/null +++ b/artemis/resources/showcase_ui/index.html @@ -0,0 +1,37 @@ + + + + + + + artemis + + + + + + + + + + + + + + + + diff --git a/artemis/resources/showcase_ui/logo.png b/artemis/resources/showcase_ui/logo.png new file mode 100644 index 0000000..2d96e14 Binary files /dev/null and b/artemis/resources/showcase_ui/logo.png differ diff --git a/artemis/resources/showcase_ui/logo.svg b/artemis/resources/showcase_ui/logo.svg new file mode 100644 index 0000000..53a9a18 --- /dev/null +++ b/artemis/resources/showcase_ui/logo.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/artemis/resources/showcase_ui/main-BGAFACUZ.js b/artemis/resources/showcase_ui/main-BGAFACUZ.js new file mode 100644 index 0000000..8ae6a1d --- /dev/null +++ b/artemis/resources/showcase_ui/main-BGAFACUZ.js @@ -0,0 +1,18 @@ +var aO=Object.defineProperty,sO=Object.defineProperties;var cO=Object.getOwnPropertyDescriptors;var vm=Object.getOwnPropertySymbols;var lO=Object.prototype.hasOwnProperty,dO=Object.prototype.propertyIsEnumerable;var ym=(n,t,e)=>t in n?aO(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e,S=(n,t)=>{for(var e in t||={})lO.call(t,e)&&ym(n,e,t[e]);if(vm)for(var e of vm(t))dO.call(t,e)&&ym(n,e,t[e]);return n},F=(n,t)=>sO(n,cO(t));var be=(n,t,e)=>new Promise((o,i)=>{var r=c=>{try{s(e.next(c))}catch(p){i(p)}},a=c=>{try{s(e.throw(c))}catch(p){i(p)}},s=c=>c.done?o(c.value):Promise.resolve(c.value).then(r,a);s((e=e.apply(n,t)).next())});var mt=null,Ds=!1,$o=1,uO=null,nt=Symbol("SIGNAL");function Y(n){let t=mt;return mt=n,t}function Is(){return mt}var po={version:0,lastCleanEpoch:0,dirty:!1,producers:void 0,producersTail:void 0,consumers:void 0,consumersTail:void 0,recomputing:!1,consumerAllowSignalWrites:!1,consumerIsAlwaysLive:!1,kind:"unknown",producerMustRecompute:()=>!1,producerRecomputeValue:()=>{},consumerMarkedDirty:()=>{},consumerOnSignalRead:()=>{}};function Ho(n){if(Ds)throw new Error("");if(mt===null)return;mt.consumerOnSignalRead(n);let t=mt.producersTail;if(t!==void 0&&t.producer===n)return;let e,o=mt.recomputing;if(o&&(e=t!==void 0?t.nextProducer:mt.producers,e!==void 0&&e.producer===n)){mt.producersTail=e,e.lastReadVersion=n.version,e.knownValidAtEpoch=$o;return}let i=n.consumersTail;if(i!==void 0&&i.consumer===mt&&(!o||i.knownValidAtEpoch===$o))return;let r=$i(mt),a={producer:n,consumer:mt,nextProducer:e,prevConsumer:void 0,knownValidAtEpoch:$o,lastReadVersion:n.version,nextConsumer:void 0};mt.producersTail=a,t!==void 0?t.nextProducer=a:mt.producers=a,r&&km(n,a)}function xm(){$o++}function Uo(n){if(!($i(n)&&!n.dirty)&&!(!n.dirty&&n.lastCleanEpoch===$o)){if(!n.producerMustRecompute(n)&&!Wr(n)){ji(n);return}n.producerRecomputeValue(n),ji(n)}}function eu(n){if(n.consumers===void 0)return;let t=Ds;Ds=!0;try{for(let e=n.consumers;e!==void 0;e=e.nextConsumer){let o=e.consumer;o.dirty||gO(o)}}finally{Ds=t}}function tu(){return mt?.consumerAllowSignalWrites!==!1}function gO(n){n.dirty=!0,eu(n),n.consumerMarkedDirty?.(n)}function ji(n){n.dirty=!1,n.lastCleanEpoch=$o}function fo(n){return n&&wm(n),Y(n)}function wm(n){if(n.producersTail?.knownValidAtEpoch===$o){let t=n.producers;for(;t!==void 0;)t.knownValidAtEpoch=null,t=t.nextProducer}n.producersTail=void 0,n.recomputing=!0}function Go(n,t){Y(t),n&&Sm(n)}function Sm(n){n.recomputing=!1;let t=n.producersTail,e=t!==void 0?t.nextProducer:n.producers;if(e!==void 0){if($i(n))do e=nu(e);while(e!==void 0);t!==void 0?t.nextProducer=void 0:n.producers=void 0}}function Wr(n){for(let t=n.producers;t!==void 0;t=t.nextProducer){let e=t.producer,o=t.lastReadVersion;if(o!==e.version||(Uo(e),o!==e.version))return!0}return!1}function qo(n){if($i(n)){let t=n.producers;for(;t!==void 0;)t=nu(t)}n.producers=void 0,n.producersTail=void 0,n.consumers=void 0,n.consumersTail=void 0}function km(n,t){let e=n.consumersTail,o=$i(n);if(e!==void 0?(t.nextConsumer=e.nextConsumer,e.nextConsumer=t):(t.nextConsumer=void 0,n.consumers=t),t.prevConsumer=e,n.consumersTail=t,!o)for(let i=n.producers;i!==void 0;i=i.nextProducer)km(i.producer,i)}function nu(n){let t=n.producer,e=n.nextProducer,o=n.nextConsumer,i=n.prevConsumer;if(n.nextConsumer=void 0,n.prevConsumer=void 0,o!==void 0?o.prevConsumer=i:t.consumersTail=i,i!==void 0)i.nextConsumer=o;else if(t.consumers=o,!$i(t)){let r=t.producers;for(;r!==void 0;)r=nu(r)}return e}function $i(n){return n.consumerIsAlwaysLive||n.consumers!==void 0}function Kr(n){uO?.(n)}function Yr(n,t){return Object.is(n,t)}function Ts(n,t){let e=Object.create(pO);e.computation=n,t!==void 0&&(e.equal=t);let o=()=>{if(Uo(e),Ho(e),e.value===Cn)throw e.error;return e.value};return o[nt]=e,Kr(e),o}var zo=Symbol("UNSET"),Bo=Symbol("COMPUTING"),Cn=Symbol("ERRORED"),pO=F(S({},po),{value:zo,dirty:!0,error:null,equal:Yr,kind:"computed",producerMustRecompute(n){return n.value===zo||n.value===Bo},producerRecomputeValue(n){if(n.value===Bo)throw new Error("");let t=n.value;n.value=Bo;let e=fo(n),o,i=!1;try{o=n.computation(),Y(null),i=t!==zo&&t!==Cn&&o!==Cn&&n.equal(t,o)}catch(r){o=Cn,n.error=r}finally{Go(n,e)}if(i){n.value=t;return}n.value=o,n.version++}});function fO(){throw new Error}var Em=fO;function Dm(n){Em(n)}function ou(n){Em=n}var mO=null;function iu(n,t){let e=Object.create(Ns);e.value=n,t!==void 0&&(e.equal=t);let o=()=>Im(e);return o[nt]=e,Kr(e),[o,a=>Wo(e,a),a=>As(e,a)]}function Im(n){return Ho(n),n.value}function Wo(n,t){tu()||Dm(n),n.equal(n.value,t)||(n.value=t,_O(n))}function As(n,t){tu()||Dm(n),Wo(n,t(n.value))}var Ns=F(S({},po),{equal:Yr,value:void 0,kind:"signal"});function _O(n){n.version++,xm(),eu(n),mO?.(n)}var ru=F(S({},po),{consumerIsAlwaysLive:!0,consumerAllowSignalWrites:!0,dirty:!0,kind:"effect"});function au(n){if(n.dirty=!1,n.version>0&&!Wr(n))return;n.version++;let t=fo(n);try{n.cleanup(),n.fn()}finally{Go(n,t)}}var su;function Rs(){return su}function Mn(n){let t=su;return su=n,t}var Tm=Symbol("NotFound");function zi(n){return n===Tm||n?.name==="\u0275NotFound"}function cu(n,t,e){let o=Object.create(hO);o.source=n,o.computation=t,e!=null&&(o.equal=e);let r=()=>{if(Uo(o),Ho(o),o.value===Cn)throw o.error;return o.value};return r[nt]=o,Kr(o),r}function lu(n,t){Uo(n),Wo(n,t),ji(n)}function Am(n,t){if(Uo(n),n.value===Cn)throw n.error;As(n,t),ji(n)}var hO=F(S({},po),{value:zo,dirty:!0,error:null,equal:Yr,kind:"linkedSignal",producerMustRecompute(n){return n.value===zo||n.value===Bo},producerRecomputeValue(n){if(n.value===Bo)throw new Error("");let t=n.value;n.value=Bo;let e=fo(n),o,i=!1;try{let r=n.source(),a=t!==zo&&t!==Cn,s=a?{source:n.sourceValue,value:t}:void 0;o=n.computation(r,s),n.sourceValue=r,Y(null),i=a&&o!==Cn&&n.equal(t,o)}catch(r){o=Cn,n.error=r}finally{Go(n,e)}if(i){n.value=t;return}n.value=o,n.version++}});function Nm(n){let t=Y(null);try{return n()}finally{Y(t)}}function ae(n){return typeof n=="function"}function Bi(n){let e=n(o=>{Error.call(o),o.stack=new Error().stack});return e.prototype=Object.create(Error.prototype),e.prototype.constructor=e,e}var Fs=Bi(n=>function(e){n(this),this.message=e?`${e.length} errors occurred during unsubscription: +${e.map((o,i)=>`${i+1}) ${o.toString()}`).join(` + `)}`:"",this.name="UnsubscriptionError",this.errors=e});function Zr(n,t){if(n){let e=n.indexOf(t);0<=e&&n.splice(e,1)}}var Be=class n{constructor(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}unsubscribe(){let t;if(!this.closed){this.closed=!0;let{_parentage:e}=this;if(e)if(this._parentage=null,Array.isArray(e))for(let r of e)r.remove(this);else e.remove(this);let{initialTeardown:o}=this;if(ae(o))try{o()}catch(r){t=r instanceof Fs?r.errors:[r]}let{_finalizers:i}=this;if(i){this._finalizers=null;for(let r of i)try{Rm(r)}catch(a){t=t??[],a instanceof Fs?t=[...t,...a.errors]:t.push(a)}}if(t)throw new Fs(t)}}add(t){var e;if(t&&t!==this)if(this.closed)Rm(t);else{if(t instanceof n){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(e=this._finalizers)!==null&&e!==void 0?e:[]).push(t)}}_hasParent(t){let{_parentage:e}=this;return e===t||Array.isArray(e)&&e.includes(t)}_addParent(t){let{_parentage:e}=this;this._parentage=Array.isArray(e)?(e.push(t),e):e?[e,t]:t}_removeParent(t){let{_parentage:e}=this;e===t?this._parentage=null:Array.isArray(e)&&Zr(e,t)}remove(t){let{_finalizers:e}=this;e&&Zr(e,t),t instanceof n&&t._removeParent(this)}};Be.EMPTY=(()=>{let n=new Be;return n.closed=!0,n})();var du=Be.EMPTY;function Ls(n){return n instanceof Be||n&&"closed"in n&&ae(n.remove)&&ae(n.add)&&ae(n.unsubscribe)}function Rm(n){ae(n)?n():n.unsubscribe()}var en={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var Hi={setTimeout(n,t,...e){let{delegate:o}=Hi;return o?.setTimeout?o.setTimeout(n,t,...e):setTimeout(n,t,...e)},clearTimeout(n){let{delegate:t}=Hi;return(t?.clearTimeout||clearTimeout)(n)},delegate:void 0};function Vs(n){Hi.setTimeout(()=>{let{onUnhandledError:t}=en;if(t)t(n);else throw n})}function Qr(){}var Fm=uu("C",void 0,void 0);function Lm(n){return uu("E",void 0,n)}function Vm(n){return uu("N",n,void 0)}function uu(n,t,e){return{kind:n,value:t,error:e}}var Ko=null;function Ui(n){if(en.useDeprecatedSynchronousErrorHandling){let t=!Ko;if(t&&(Ko={errorThrown:!1,error:null}),n(),t){let{errorThrown:e,error:o}=Ko;if(Ko=null,e)throw o}}else n()}function jm(n){en.useDeprecatedSynchronousErrorHandling&&Ko&&(Ko.errorThrown=!0,Ko.error=n)}var Yo=class extends Be{constructor(t){super(),this.isStopped=!1,t?(this.destination=t,Ls(t)&&t.add(this)):this.destination=bO}static create(t,e,o){return new zn(t,e,o)}next(t){this.isStopped?pu(Vm(t),this):this._next(t)}error(t){this.isStopped?pu(Lm(t),this):(this.isStopped=!0,this._error(t))}complete(){this.isStopped?pu(Fm,this):(this.isStopped=!0,this._complete())}unsubscribe(){this.closed||(this.isStopped=!0,super.unsubscribe(),this.destination=null)}_next(t){this.destination.next(t)}_error(t){try{this.destination.error(t)}finally{this.unsubscribe()}}_complete(){try{this.destination.complete()}finally{this.unsubscribe()}}},CO=Function.prototype.bind;function gu(n,t){return CO.call(n,t)}var fu=class{constructor(t){this.partialObserver=t}next(t){let{partialObserver:e}=this;if(e.next)try{e.next(t)}catch(o){js(o)}}error(t){let{partialObserver:e}=this;if(e.error)try{e.error(t)}catch(o){js(o)}else js(t)}complete(){let{partialObserver:t}=this;if(t.complete)try{t.complete()}catch(e){js(e)}}},zn=class extends Yo{constructor(t,e,o){super();let i;if(ae(t)||!t)i={next:t??void 0,error:e??void 0,complete:o??void 0};else{let r;this&&en.useDeprecatedNextContext?(r=Object.create(t),r.unsubscribe=()=>this.unsubscribe(),i={next:t.next&&gu(t.next,r),error:t.error&&gu(t.error,r),complete:t.complete&&gu(t.complete,r)}):i=t}this.destination=new fu(i)}};function js(n){en.useDeprecatedSynchronousErrorHandling?jm(n):Vs(n)}function MO(n){throw n}function pu(n,t){let{onStoppedNotification:e}=en;e&&Hi.setTimeout(()=>e(n,t))}var bO={closed:!0,next:Qr,error:MO,complete:Qr};var Gi=typeof Symbol=="function"&&Symbol.observable||"@@observable";function tn(n){return n}function $s(...n){return mu(n)}function mu(n){return n.length===0?tn:n.length===1?n[0]:function(e){return n.reduce((o,i)=>i(o),e)}}var ie=class n{constructor(t){t&&(this._subscribe=t)}lift(t){let e=new n;return e.source=this,e.operator=t,e}subscribe(t,e,o){let i=OO(t)?t:new zn(t,e,o);return Ui(()=>{let{operator:r,source:a}=this;i.add(r?r.call(i,a):a?this._subscribe(i):this._trySubscribe(i))}),i}_trySubscribe(t){try{return this._subscribe(t)}catch(e){t.error(e)}}forEach(t,e){return e=$m(e),new e((o,i)=>{let r=new zn({next:a=>{try{t(a)}catch(s){i(s),r.unsubscribe()}},error:i,complete:o});this.subscribe(r)})}_subscribe(t){var e;return(e=this.source)===null||e===void 0?void 0:e.subscribe(t)}[Gi](){return this}pipe(...t){return mu(t)(this)}toPromise(t){return t=$m(t),new t((e,o)=>{let i;this.subscribe(r=>i=r,r=>o(r),()=>e(i))})}};ie.create=n=>new ie(n);function $m(n){var t;return(t=n??en.Promise)!==null&&t!==void 0?t:Promise}function PO(n){return n&&ae(n.next)&&ae(n.error)&&ae(n.complete)}function OO(n){return n&&n instanceof Yo||PO(n)&&Ls(n)}function vO(n){return ae(n?.lift)}function fe(n){return t=>{if(vO(t))return t.lift(function(e){try{return n(e,this)}catch(o){this.error(o)}});throw new TypeError("Unable to lift unknown Observable type")}}function _e(n,t,e,o,i){return new _u(n,t,e,o,i)}var _u=class extends Yo{constructor(t,e,o,i,r,a){super(t),this.onFinalize=r,this.shouldUnsubscribe=a,this._next=e?function(s){try{e(s)}catch(c){t.error(c)}}:super._next,this._error=i?function(s){try{i(s)}catch(c){t.error(c)}finally{this.unsubscribe()}}:super._error,this._complete=o?function(){try{o()}catch(s){t.error(s)}finally{this.unsubscribe()}}:super._complete}unsubscribe(){var t;if(!this.shouldUnsubscribe||this.shouldUnsubscribe()){let{closed:e}=this;super.unsubscribe(),!e&&((t=this.onFinalize)===null||t===void 0||t.call(this))}}};var zm=Bi(n=>function(){n(this),this.name="ObjectUnsubscribedError",this.message="object unsubscribed"});var ye=class extends ie{constructor(){super(),this.closed=!1,this.currentObservers=null,this.observers=[],this.isStopped=!1,this.hasError=!1,this.thrownError=null}lift(t){let e=new zs(this,this);return e.operator=t,e}_throwIfClosed(){if(this.closed)throw new zm}next(t){Ui(()=>{if(this._throwIfClosed(),!this.isStopped){this.currentObservers||(this.currentObservers=Array.from(this.observers));for(let e of this.currentObservers)e.next(t)}})}error(t){Ui(()=>{if(this._throwIfClosed(),!this.isStopped){this.hasError=this.isStopped=!0,this.thrownError=t;let{observers:e}=this;for(;e.length;)e.shift().error(t)}})}complete(){Ui(()=>{if(this._throwIfClosed(),!this.isStopped){this.isStopped=!0;let{observers:t}=this;for(;t.length;)t.shift().complete()}})}unsubscribe(){this.isStopped=this.closed=!0,this.observers=this.currentObservers=null}get observed(){var t;return((t=this.observers)===null||t===void 0?void 0:t.length)>0}_trySubscribe(t){return this._throwIfClosed(),super._trySubscribe(t)}_subscribe(t){return this._throwIfClosed(),this._checkFinalizedStatuses(t),this._innerSubscribe(t)}_innerSubscribe(t){let{hasError:e,isStopped:o,observers:i}=this;return e||o?du:(this.currentObservers=null,i.push(t),new Be(()=>{this.currentObservers=null,Zr(i,t)}))}_checkFinalizedStatuses(t){let{hasError:e,thrownError:o,isStopped:i}=this;e?t.error(o):i&&t.complete()}asObservable(){let t=new ie;return t.source=this,t}};ye.create=(n,t)=>new zs(n,t);var zs=class extends ye{constructor(t,e){super(),this.destination=t,this.source=e}next(t){var e,o;(o=(e=this.destination)===null||e===void 0?void 0:e.next)===null||o===void 0||o.call(e,t)}error(t){var e,o;(o=(e=this.destination)===null||e===void 0?void 0:e.error)===null||o===void 0||o.call(e,t)}complete(){var t,e;(e=(t=this.destination)===null||t===void 0?void 0:t.complete)===null||e===void 0||e.call(t)}_subscribe(t){var e,o;return(o=(e=this.source)===null||e===void 0?void 0:e.subscribe(t))!==null&&o!==void 0?o:du}};var st=class extends ye{constructor(t){super(),this._value=t}get value(){return this.getValue()}_subscribe(t){let e=super._subscribe(t);return!e.closed&&t.next(this._value),e}getValue(){let{hasError:t,thrownError:e,_value:o}=this;if(t)throw e;return this._throwIfClosed(),o}next(t){super.next(this._value=t)}};var hu={now(){return(hu.delegate||Date).now()},delegate:void 0};var Bs=class extends ye{constructor(t=1/0,e=1/0,o=hu){super(),this._bufferSize=t,this._windowTime=e,this._timestampProvider=o,this._buffer=[],this._infiniteTimeWindow=!0,this._infiniteTimeWindow=e===1/0,this._bufferSize=Math.max(1,t),this._windowTime=Math.max(1,e)}next(t){let{isStopped:e,_buffer:o,_infiniteTimeWindow:i,_timestampProvider:r,_windowTime:a}=this;e||(o.push(t),!i&&o.push(r.now()+a)),this._trimBuffer(),super.next(t)}_subscribe(t){this._throwIfClosed(),this._trimBuffer();let e=this._innerSubscribe(t),{_infiniteTimeWindow:o,_buffer:i}=this,r=i.slice();for(let a=0;an.complete());function Bm(n){return n&&ae(n.schedule)}function Hm(n){return n[n.length-1]}function Hs(n){return ae(Hm(n))?n.pop():void 0}function mo(n){return Bm(Hm(n))?n.pop():void 0}function Gm(n,t,e,o){function i(r){return r instanceof e?r:new e(function(a){a(r)})}return new(e||(e=Promise))(function(r,a){function s(f){try{p(o.next(f))}catch(_){a(_)}}function c(f){try{p(o.throw(f))}catch(_){a(_)}}function p(f){f.done?r(f.value):i(f.value).then(s,c)}p((o=o.apply(n,t||[])).next())})}function Um(n){var t=typeof Symbol=="function"&&Symbol.iterator,e=t&&n[t],o=0;if(e)return e.call(n);if(n&&typeof n.length=="number")return{next:function(){return n&&o>=n.length&&(n=void 0),{value:n&&n[o++],done:!n}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function Zo(n){return this instanceof Zo?(this.v=n,this):new Zo(n)}function qm(n,t,e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var o=e.apply(n,t||[]),i,r=[];return i=Object.create((typeof AsyncIterator=="function"?AsyncIterator:Object).prototype),s("next"),s("throw"),s("return",a),i[Symbol.asyncIterator]=function(){return this},i;function a(h){return function(P){return Promise.resolve(P).then(h,_)}}function s(h,P){o[h]&&(i[h]=function(T){return new Promise(function(A,N){r.push([h,T,A,N])>1||c(h,T)})},P&&(i[h]=P(i[h])))}function c(h,P){try{p(o[h](P))}catch(T){C(r[0][3],T)}}function p(h){h.value instanceof Zo?Promise.resolve(h.value.v).then(f,_):C(r[0][2],h)}function f(h){c("next",h)}function _(h){c("throw",h)}function C(h,P){h(P),r.shift(),r.length&&c(r[0][0],r[0][1])}}function Wm(n){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=n[Symbol.asyncIterator],e;return t?t.call(n):(n=typeof Um=="function"?Um(n):n[Symbol.iterator](),e={},o("next"),o("throw"),o("return"),e[Symbol.asyncIterator]=function(){return this},e);function o(r){e[r]=n[r]&&function(a){return new Promise(function(s,c){a=n[r](a),i(s,c,a.done,a.value)})}}function i(r,a,s,c){Promise.resolve(c).then(function(p){r({value:p,done:s})},a)}}var Us=(n=>n&&typeof n.length=="number"&&typeof n!="function");function Gs(n){return ae(n?.then)}function qs(n){return ae(n[Gi])}function Ws(n){return Symbol.asyncIterator&&ae(n?.[Symbol.asyncIterator])}function Ks(n){return new TypeError(`You provided ${n!==null&&typeof n=="object"?"an invalid object":`'${n}'`} where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.`)}function yO(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Ys=yO();function Zs(n){return ae(n?.[Ys])}function Qs(n){return qm(this,arguments,function*(){let e=n.getReader();try{for(;;){let{value:o,done:i}=yield Zo(e.read());if(i)return yield Zo(void 0);yield yield Zo(o)}}finally{e.releaseLock()}})}function Js(n){return ae(n?.getReader)}function Re(n){if(n instanceof ie)return n;if(n!=null){if(qs(n))return xO(n);if(Us(n))return wO(n);if(Gs(n))return SO(n);if(Ws(n))return Km(n);if(Zs(n))return kO(n);if(Js(n))return EO(n)}throw Ks(n)}function xO(n){return new ie(t=>{let e=n[Gi]();if(ae(e.subscribe))return e.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function wO(n){return new ie(t=>{for(let e=0;e{n.then(e=>{t.closed||(t.next(e),t.complete())},e=>t.error(e)).then(null,Vs)})}function kO(n){return new ie(t=>{for(let e of n)if(t.next(e),t.closed)return;t.complete()})}function Km(n){return new ie(t=>{DO(n,t).catch(e=>t.error(e))})}function EO(n){return Km(Qs(n))}function DO(n,t){var e,o,i,r;return Gm(this,void 0,void 0,function*(){try{for(e=Wm(n);o=yield e.next(),!o.done;){let a=o.value;if(t.next(a),t.closed)return}}catch(a){i={error:a}}finally{try{o&&!o.done&&(r=e.return)&&(yield r.call(e))}finally{if(i)throw i.error}}t.complete()})}function Ot(n,t,e,o=0,i=!1){let r=t.schedule(function(){e(),i?n.add(this.schedule(null,o)):this.unsubscribe()},o);if(n.add(r),!i)return r}function Xs(n,t=0){return fe((e,o)=>{e.subscribe(_e(o,i=>Ot(o,n,()=>o.next(i),t),()=>Ot(o,n,()=>o.complete(),t),i=>Ot(o,n,()=>o.error(i),t)))})}function ec(n,t=0){return fe((e,o)=>{o.add(n.schedule(()=>e.subscribe(o),t))})}function Ym(n,t){return Re(n).pipe(ec(t),Xs(t))}function Zm(n,t){return Re(n).pipe(ec(t),Xs(t))}function Qm(n,t){return new ie(e=>{let o=0;return t.schedule(function(){o===n.length?e.complete():(e.next(n[o++]),e.closed||this.schedule())})})}function Jm(n,t){return new ie(e=>{let o;return Ot(e,t,()=>{o=n[Ys](),Ot(e,t,()=>{let i,r;try{({value:i,done:r}=o.next())}catch(a){e.error(a);return}r?e.complete():e.next(i)},0,!0)}),()=>ae(o?.return)&&o.return()})}function tc(n,t){if(!n)throw new Error("Iterable cannot be null");return new ie(e=>{Ot(e,t,()=>{let o=n[Symbol.asyncIterator]();Ot(e,t,()=>{o.next().then(i=>{i.done?e.complete():e.next(i.value)})},0,!0)})})}function Xm(n,t){return tc(Qs(n),t)}function e_(n,t){if(n!=null){if(qs(n))return Ym(n,t);if(Us(n))return Qm(n,t);if(Gs(n))return Zm(n,t);if(Ws(n))return tc(n,t);if(Zs(n))return Jm(n,t);if(Js(n))return Xm(n,t)}throw Ks(n)}function Fe(n,t){return t?e_(n,t):Re(n)}function se(...n){let t=mo(n);return Fe(n,t)}function Cu(n,t){let e=ae(n)?n:()=>n,o=i=>i.error(e());return new ie(t?i=>t.schedule(o,0,i):o)}function nc(n){return!!n&&(n instanceof ie||ae(n.lift)&&ae(n.subscribe))}var Qo=Bi(n=>function(){n(this),this.name="EmptyError",this.message="no elements in sequence"});function Pe(n,t){return fe((e,o)=>{let i=0;e.subscribe(_e(o,r=>{o.next(n.call(t,r,i++))}))})}var{isArray:IO}=Array;function TO(n,t){return IO(t)?n(...t):n(t)}function oc(n){return Pe(t=>TO(n,t))}var{isArray:AO}=Array,{getPrototypeOf:NO,prototype:RO,keys:FO}=Object;function ic(n){if(n.length===1){let t=n[0];if(AO(t))return{args:t,keys:null};if(LO(t)){let e=FO(t);return{args:e.map(o=>t[o]),keys:e}}}return{args:n,keys:null}}function LO(n){return n&&typeof n=="object"&&NO(n)===RO}function rc(n,t){return n.reduce((e,o,i)=>(e[o]=t[i],e),{})}function Mu(...n){let t=mo(n),e=Hs(n),{args:o,keys:i}=ic(n);if(o.length===0)return Fe([],t);let r=new ie(VO(o,t,i?a=>rc(i,a):tn));return e?r.pipe(oc(e)):r}function VO(n,t,e=tn){return o=>{t_(t,()=>{let{length:i}=n,r=new Array(i),a=i,s=i;for(let c=0;c{let p=Fe(n[c],t),f=!1;p.subscribe(_e(o,_=>{r[c]=_,f||(f=!0,s--),s||o.next(e(r.slice()))},()=>{--a||o.complete()}))},o)},o)}}function t_(n,t,e){n?Ot(e,n,t):t()}function n_(n,t,e,o,i,r,a,s){let c=[],p=0,f=0,_=!1,C=()=>{_&&!c.length&&!p&&t.complete()},h=T=>p{r&&t.next(T),p++;let A=!1;Re(e(T,f++)).subscribe(_e(t,N=>{i?.(N),r?h(N):t.next(N)},()=>{A=!0},void 0,()=>{if(A)try{for(p--;c.length&&pP(N)):P(N)}C()}catch(N){t.error(N)}}))};return n.subscribe(_e(t,h,()=>{_=!0,C()})),()=>{s?.()}}function ut(n,t,e=1/0){return ae(t)?ut((o,i)=>Pe((r,a)=>t(o,r,i,a))(Re(n(o,i))),e):(typeof t=="number"&&(e=t),fe((o,i)=>n_(o,i,n,e)))}function qi(n=1/0){return ut(tn,n)}function o_(){return qi(1)}function Wi(...n){return o_()(Fe(n,mo(n)))}function Jr(n){return new ie(t=>{Re(n()).subscribe(t)})}function bu(...n){let t=Hs(n),{args:e,keys:o}=ic(n),i=new ie(r=>{let{length:a}=e;if(!a){r.complete();return}let s=new Array(a),c=a,p=a;for(let f=0;f{_||(_=!0,p--),s[f]=C},()=>c--,void 0,()=>{(!c||!_)&&(p||r.next(o?rc(o,s):s),r.complete())}))}});return t?i.pipe(oc(t)):i}function Bt(n,t){return fe((e,o)=>{let i=0;e.subscribe(_e(o,r=>n.call(t,r,i++)&&o.next(r)))})}function Ki(n){return fe((t,e)=>{let o=null,i=!1,r;o=t.subscribe(_e(e,void 0,void 0,a=>{r=Re(n(a,Ki(n)(t))),o?(o.unsubscribe(),o=null,r.subscribe(e)):i=!0})),i&&(o.unsubscribe(),o=null,r.subscribe(e))})}function Jo(n,t){return ae(t)?ut(n,t,1):ut(n,1)}function i_(n){return fe((t,e)=>{let o=!1;t.subscribe(_e(e,i=>{o=!0,e.next(i)},()=>{o||e.next(n),e.complete()}))})}function Bn(n){return n<=0?()=>ct:fe((t,e)=>{let o=0;t.subscribe(_e(e,i=>{++o<=n&&(e.next(i),n<=o&&e.complete())}))})}function r_(n=jO){return fe((t,e)=>{let o=!1;t.subscribe(_e(e,i=>{o=!0,e.next(i)},()=>o?e.complete():e.error(n())))})}function jO(){return new Qo}function _o(n){return fe((t,e)=>{try{t.subscribe(e)}finally{e.add(n)}})}function Hn(n,t){let e=arguments.length>=2;return o=>o.pipe(n?Bt((i,r)=>n(i,r,o)):tn,Bn(1),e?i_(t):r_(()=>new Qo))}function ac(n){return n<=0?()=>ct:fe((t,e)=>{let o=[];t.subscribe(_e(e,i=>{o.push(i),n{for(let i of o)e.next(i);e.complete()},void 0,()=>{o=null}))})}function a_(n={}){let{connector:t=()=>new ye,resetOnError:e=!0,resetOnComplete:o=!0,resetOnRefCountZero:i=!0}=n;return r=>{let a,s,c,p=0,f=!1,_=!1,C=()=>{s?.unsubscribe(),s=void 0},h=()=>{C(),a=c=void 0,f=_=!1},P=()=>{let T=a;h(),T?.unsubscribe()};return fe((T,A)=>{p++,!_&&!f&&C();let N=c=c??t();A.add(()=>{p--,p===0&&!_&&!f&&(s=Pu(P,i))}),N.subscribe(A),!a&&p>0&&(a=new zn({next:E=>N.next(E),error:E=>{_=!0,C(),s=Pu(h,e,E),N.error(E)},complete:()=>{f=!0,C(),s=Pu(h,o),N.complete()}}),Re(T).subscribe(a))})(r)}}function Pu(n,t,...e){if(t===!0){n();return}if(t===!1)return;let o=new zn({next:()=>{o.unsubscribe(),n()}});return Re(t(...e)).subscribe(o)}function Ou(n,t,e){let o,i=!1;return n&&typeof n=="object"?{bufferSize:o=1/0,windowTime:t=1/0,refCount:i=!1,scheduler:e}=n:o=n??1/0,a_({connector:()=>new Bs(o,t,e),resetOnError:!0,resetOnComplete:!1,resetOnRefCountZero:i})}function vu(...n){let t=mo(n);return fe((e,o)=>{(t?Wi(n,e,t):Wi(n,e)).subscribe(o)})}function vt(n,t){return fe((e,o)=>{let i=null,r=0,a=!1,s=()=>a&&!i&&o.complete();e.subscribe(_e(o,c=>{i?.unsubscribe();let p=0,f=r++;Re(n(c,f)).subscribe(i=_e(o,_=>o.next(t?t(c,_,f,p++):_),()=>{i=null,s()}))},()=>{a=!0,s()}))})}function Xr(n){return fe((t,e)=>{Re(n).subscribe(_e(e,()=>e.complete(),Qr)),!e.closed&&t.subscribe(e)})}function xe(n,t,e){let o=ae(n)||t||e?{next:n,error:t,complete:e}:n;return o?fe((i,r)=>{var a;(a=o.subscribe)===null||a===void 0||a.call(o);let s=!0;i.subscribe(_e(r,c=>{var p;(p=o.next)===null||p===void 0||p.call(o,c),r.next(c)},()=>{var c;s=!1,(c=o.complete)===null||c===void 0||c.call(o),r.complete()},c=>{var p;s=!1,(p=o.error)===null||p===void 0||p.call(o,c),r.error(c)},()=>{var c,p;s&&((c=o.unsubscribe)===null||c===void 0||c.call(o)),(p=o.finalize)===null||p===void 0||p.call(o)}))}):tn}var pc="https://angular.dev/best-practices/security#preventing-cross-site-scripting-xss",z=class extends Error{code;constructor(t,e){super(Wn(t,e)),this.code=t}};function $O(n){return`NG0${Math.abs(n)}`}function Wn(n,t){return`${$O(n)}${t?": "+t:""}`}function he(n){for(let t in n)if(n[t]===he)return t;throw Error("")}function g_(n,t){for(let e in t)Object.hasOwn(t,e)&&!Object.hasOwn(n,e)&&(n[e]=t[e])}function aa(n){if(typeof n=="string")return n;if(Array.isArray(n))return`[${n.map(aa).join(", ")}]`;if(n==null)return""+n;let t=n.overriddenName||n.name;if(t)return`${t}`;let e=n.toString();if(e==null)return""+e;let o=e.indexOf(` +`);return o>=0?e.slice(0,o):e}function fc(n,t){return n?t?`${n} ${t}`:n:t||""}var zO=he({__forward_ref__:he});function on(n){return n.__forward_ref__=on,n}function lt(n){return Lu(n)?n():n}function Lu(n){return typeof n=="function"&&Object.hasOwn(n,zO)&&n.__forward_ref__===on}function te(n){return{token:n.token,providedIn:n.providedIn||null,factory:n.factory,value:void 0}}function Pn(n){return{providers:n.providers||[],imports:n.imports||[]}}function sa(n){return BO(n,mc)}function Vu(n){return sa(n)!==null}function BO(n,t){return Object.hasOwn(n,t)&&n[t]||null}function HO(n){let t=n?.[mc]??null;return t||null}function xu(n){return n&&Object.hasOwn(n,cc)?n[cc]:null}var mc=he({\u0275prov:he}),cc=he({\u0275inj:he}),B=class{_desc;ngMetadataName="InjectionToken";\u0275prov;constructor(t,e){this._desc=t,this.\u0275prov=void 0,typeof e=="number"?this.__NG_ELEMENT_ID__=e:e!==void 0&&(this.\u0275prov=te({token:this,providedIn:e.providedIn||"root",factory:e.factory}))}get multi(){return this}toString(){return`InjectionToken ${this._desc}`}};function ju(n){return n&&!!n.\u0275providers}var ca=he({\u0275cmp:he}),la=he({\u0275dir:he}),$u=he({\u0275pipe:he}),zu=he({\u0275mod:he}),na=he({\u0275fac:he}),oi=he({__NG_ELEMENT_ID__:he}),s_=he({__NG_ENV_ID__:he});function p_(n){return _c(n,"@NgModule"),n[zu]||null}function ii(n){return _c(n,"@Component"),n[ca]||null}function Bu(n){return _c(n,"@Directive"),n[la]||null}function f_(n){return _c(n,"@Pipe"),n[$u]||null}function _c(n,t){if(n==null)throw new z(-919,!1)}function rn(n){return typeof n=="string"?n:n==null?"":String(n)}var m_=he({ngErrorCode:he}),UO=he({ngErrorMessage:he}),GO=he({ngTokenPath:he});function Hu(n,t){return __("",-200,t)}function hc(n,t){throw new z(-201,!1)}function __(n,t,e){let o=new z(t,n);return o[m_]=t,o[UO]=n,e&&(o[GO]=e),o}function qO(n){return n[m_]}var wu;function h_(){return wu}function _t(n){let t=wu;return wu=n,t}function Uu(n,t,e){let o=sa(n);if(o&&o.providedIn=="root")return o.value===void 0?o.value=o.factory():o.value;if(e&8)return null;if(t!==void 0)return t;hc(n,"")}var an=globalThis;var WO={},Xo=WO,KO="__NG_DI_FLAG__",Su=class{injector;constructor(t){this.injector=t}retrieve(t,e){let o=ei(e)||0;try{return this.injector.get(t,o&8?null:Xo,o)}catch(i){if(zi(i))return i;throw i}}};function YO(n,t=0){let e=Rs();if(e===void 0)throw new z(-203,!1);if(e===null)return Uu(n,void 0,t);{let o=ZO(t),i=e.retrieve(n,o);if(zi(i)){if(o.optional)return null;throw i}return i}}function Q(n,t=0){return(h_()||YO)(lt(n),t)}function D(n,t){return Q(n,ei(t))}function ei(n){return typeof n>"u"||typeof n=="number"?n:0|(n.optional&&8)|(n.host&&1)|(n.self&&2)|(n.skipSelf&&4)}function ZO(n){return{optional:!!(n&8),host:!!(n&1),self:!!(n&2),skipSelf:!!(n&4)}}function ku(n){let t=[];for(let e=0;eArray.isArray(e)?Cc(e,t):t(e))}function Gu(n,t,e){t>=n.length?n.push(e):n.splice(t,0,e)}function da(n,t){return t>=n.length-1?n.pop():n.splice(t,1)[0]}function b_(n,t,e,o){let i=n.length;if(i==t)n.push(e,o);else if(i===1)n.push(o,n[0]),n[0]=e;else{for(i--,n.push(n[i-1],n[i]);i>t;){let r=i-2;n[i]=n[r],i--}n[t]=e,n[t+1]=o}}function Mc(n,t,e){let o=Ji(n,t);return o>=0?n[o|1]=e:(o=~o,b_(n,o,t,e)),o}function bc(n,t){let e=Ji(n,t);if(e>=0)return n[e|1]}function Ji(n,t){return JO(n,t,1)}function JO(n,t,e){let o=0,i=n.length>>e;for(;i!==o;){let r=o+(i-o>>1),a=n[r<t?i=r:o=r+1}return~(i<{e.push(a)};return Cc(t,a=>{let s=a;lc(s,r,[],o)&&(i||=[],i.push(s))}),i!==void 0&&O_(i,r),e}function O_(n,t){for(let e=0;e{t(r,o)})}}function lc(n,t,e,o){if(n=lt(n),!n)return!1;let i=null,r=xu(n),a=!r&&ii(n);if(!r&&!a){let c=n.ngModule;if(r=xu(c),r)i=c;else return!1}else{if(a&&!a.standalone)return!1;i=n}let s=o.has(i);if(a){if(s)return!1;if(o.add(i),a.dependencies){let c=typeof a.dependencies=="function"?a.dependencies():a.dependencies;for(let p of c)lc(p,t,e,o)}}else if(r){if(r.imports!=null&&!s){o.add(i);let p;Cc(r.imports,f=>{lc(f,t,e,o)&&(p||=[],p.push(f))}),p!==void 0&&O_(p,t)}if(!s){let p=Co(i)||(()=>new i);t({provide:i,useFactory:p,deps:ht},i),t({provide:qu,useValue:i,multi:!0},i),t({provide:ri,useValue:()=>Q(i),multi:!0},i)}let c=r.providers;if(c!=null&&!s){let p=n;Ku(c,f=>{t(f,p)})}}else return!1;return i!==n&&n.providers!==void 0}function Ku(n,t){for(let e of n)ju(e)&&(e=e.\u0275providers),Array.isArray(e)?Ku(e,t):t(e)}var XO=he({provide:String,useValue:he});function v_(n){return n!==null&&typeof n=="object"&&XO in n}function ev(n){return!!(n&&n.useExisting)}function tv(n){return!!(n&&n.useFactory)}function ti(n){return typeof n=="function"}function y_(n){return!!n.useClass}var ga=new B(""),sc={},c_={},yu;function pa(){return yu===void 0&&(yu=new Zi),yu}var Le=class{},ni=class extends Le{parent;source;scopes;records=new Map;_ngOnDestroyHooks=new Set;_onDestroyHooks=[];get destroyed(){return this._destroyed}_destroyed=!1;injectorDefTypes;constructor(t,e,o,i){super(),this.parent=e,this.source=o,this.scopes=i,Du(t,a=>this.processProvider(a)),this.records.set(ua,Yi(void 0,this)),i.has("environment")&&this.records.set(Le,Yi(void 0,this));let r=this.records.get(ga);r!=null&&typeof r.value=="string"&&this.scopes.add(r.value),this.injectorDefTypes=new Set(this.get(qu,ht,{self:!0}))}retrieve(t,e){let o=ei(e)||0;try{return this.get(t,Xo,o)}catch(i){if(zi(i))return i;throw i}}destroy(){ea(this),this._destroyed=!0;let t=Y(null);try{for(let o of this._ngOnDestroyHooks)o.ngOnDestroy();let e=this._onDestroyHooks;this._onDestroyHooks=[];for(let o of e)o()}finally{this.records.clear(),this._ngOnDestroyHooks.clear(),this.injectorDefTypes.clear(),Y(t)}}onDestroy(t){return ea(this),this._onDestroyHooks.push(t),()=>this.removeOnDestroy(t)}runInContext(t){ea(this);let e=Mn(this),o=_t(void 0),i;try{return t()}finally{Mn(e),_t(o)}}get(t,e=Xo,o){if(ea(this),Object.hasOwn(t,s_))return t[s_](this);let i=ei(o),r,a=Mn(this),s=_t(void 0);try{if(!(i&4)){let p=this.records.get(t);if(p===void 0){let f=av(t)&&sa(t);f&&this.injectableDefInScope(f)?p=Yi(Eu(t),sc):p=null,this.records.set(t,p)}if(p!=null)return this.hydrate(t,p,i)}let c=i&2?pa():this.parent;return e=i&8&&e===Xo?null:e,c.get(t,e)}catch(c){let p=qO(c);throw p===-200||p===-201?new z(p,null):c}finally{_t(s),Mn(a)}}resolveInjectorInitializers(){let t=Y(null),e=Mn(this),o=_t(void 0),i;try{let r=this.get(ri,ht,{self:!0});for(let a of r)a()}finally{Mn(e),_t(o),Y(t)}}toString(){return"R3Injector[...]"}processProvider(t){t=lt(t);let e=ti(t)?t:lt(t&&t.provide),o=ov(t);if(!ti(t)&&t.multi===!0){let i=this.records.get(e);i||(i=Yi(void 0,sc,!0),i.factory=()=>ku(i.multi),this.records.set(e,i)),e=t,i.multi.push(t)}this.records.set(e,o)}hydrate(t,e,o){let i=Y(null);try{if(e.value===c_)throw Hu("");return e.value===sc&&(e.value=c_,e.value=e.factory(void 0,o)),typeof e.value=="object"&&e.value&&rv(e.value)&&this._ngOnDestroyHooks.add(e.value),e.value}finally{Y(i)}}injectableDefInScope(t){if(!t.providedIn)return!1;let e=lt(t.providedIn);return typeof e=="string"?e==="any"||this.scopes.has(e):this.injectorDefTypes.has(e)}removeOnDestroy(t){let e=this._onDestroyHooks.indexOf(t);e!==-1&&this._onDestroyHooks.splice(e,1)}};function Eu(n){let t=sa(n),e=t!==null?t.factory:Co(n);if(e!==null)return e;if(n instanceof B)throw new z(-204,!1);if(n instanceof Function)return nv(n);throw new z(-204,!1)}function nv(n){if(n.length>0)throw new z(-204,!1);let e=HO(n);return e!==null?()=>e.factory(n):()=>new n}function ov(n){if(v_(n))return Yi(void 0,n.useValue);{let t=Yu(n);return Yi(t,sc)}}function Yu(n,t,e){let o;if(ti(n)){let i=lt(n);return Co(i)||Eu(i)}else if(v_(n))o=()=>lt(n.useValue);else if(tv(n))o=()=>n.useFactory(...ku(n.deps||[]));else if(ev(n))o=(i,r)=>Q(lt(n.useExisting),r!==void 0&&r&8?8:void 0);else{let i=lt(n&&(n.useClass||n.provide));if(iv(n))o=()=>new i(...ku(n.deps));else return Co(i)||Eu(i)}return o}function ea(n){if(n.destroyed)throw new z(-205,!1)}function Yi(n,t,e=!1){return{factory:n,value:t,multi:e?[]:void 0}}function iv(n){return!!n.deps}function rv(n){return n!==null&&typeof n=="object"&&typeof n.ngOnDestroy=="function"}function av(n){return typeof n=="function"||typeof n=="object"&&n.ngMetadataName==="InjectionToken"}function Du(n,t){for(let e of n)Array.isArray(e)?Du(e,t):e&&ju(e)?Du(e.\u0275providers,t):t(e)}function et(n,t){let e;n instanceof ni?(ea(n),e=n):e=new Su(n);let o,i=Mn(e),r=_t(void 0);try{return t()}finally{Mn(i),_t(r)}}function x_(){return h_()!==void 0||Rs()!=null}var sn=0,K=1,ee=2,Xe=3,Ht=4,yt=5,Xi=6,er=7,ze=8,On=9,cn=10,we=11,tr=12,Zu=13,Po=14,xt=15,Oo=16,ai=17,vn=18,yn=19,Qu=20,Un=21,Pc=22,Gn=23,Tt=24,si=25,xn=26,He=27,w_=1,Ju=6,ci=7,fa=8,li=9,Ve=10;function Kn(n){return Array.isArray(n)&&typeof n[w_]=="object"}function Ut(n){return Array.isArray(n)&&n[w_]===!0}function Xu(n){return(n.flags&4)!==0}function Yn(n){return n.componentOffset>-1}function Oc(n){return(n.flags&1)===1}function wn(n){return!!n.template}function nr(n){return(n[ee]&512)!==0}function di(n){return(n[ee]&256)===256}var Je=(function(n){return n[n.NONE=0]="NONE",n[n.HTML=1]="HTML",n[n.STYLE=2]="STYLE",n[n.SCRIPT=3]="SCRIPT",n[n.URL=4]="URL",n[n.RESOURCE_URL=5]="RESOURCE_URL",n[n.ATTRIBUTE_NO_BINDING=6]="ATTRIBUTE_NO_BINDING",n})(Je||{}),ta,Qi="svg",vc="math",S_="",l_="*",Iu=()=>Object.create(null);function sv(){return ta||(ta=Iu(),ho(Je.HTML,void 0,[["iframe",["srcdoc"]],["*",["innerHTML","outerHTML"]]]),ho(Je.STYLE,void 0,[["*",["style"]]]),ho(Je.URL,void 0,[["*",["formAction"]],["area",["href"]],["a",["href","xlink:href"]],["form",["action"]],["img",["src"]],["video",["src"]]]),ho(Je.URL,vc,[["*",["href","xlink:href"]]]),ho(Je.RESOURCE_URL,void 0,[["base",["href"]],["embed",["src"]],["frame",["src"]],["iframe",["src"]],["link",["href"]],["object",["codebase","data"]]]),ho(Je.URL,Qi,[["a",["href","xlink:href"]]]),ho(Je.ATTRIBUTE_NO_BINDING,Qi,[["animate",["attributeName","values","to","from"]],["set",["to","attributeName"]],["animateMotion",["attributeName"]],["animateTransform",["attributeName"]]]),ho(Je.ATTRIBUTE_NO_BINDING,void 0,[["unknown",["attributeName","values","to","from","sandbox","allow","allowFullscreen","referrerPolicy","csp","fetchPriority","credentialless"]],["iframe",["sandbox","allow","allowFullscreen","referrerPolicy","csp","fetchPriority","credentialless"]]]),ta)}function ho(n,t,e){let o=t??S_;for(let[i,r]of e){let a=i.toLowerCase();for(let s of r){let c=s.toLowerCase(),p=ta[c]??=Iu(),f=p[o]??=Iu();f[a]=n}}}function k_(n,t,e){let i=sv()[t.toLowerCase()];if(!i)return Je.NONE;let r=n.toLowerCase(),a;if(e){let s=i[e];s&&(a=s[r]??s[l_])}if(a===void 0){let s=i[S_];s&&(a=s[r]??s[l_])}return a??Je.NONE}function dt(n){for(;Array.isArray(n);)n=n[sn];return n}function eg(n,t){return dt(t[n])}function At(n,t){return dt(t[n.index])}function yc(n,t){return n.data[t]}function xc(n,t){return n[t]}function tg(n,t,e,o){e>=n.data.length&&(n.data[e]=null,n.blueprint[e]=null),t[e]=o}function Gt(n,t){let e=t[n];return Kn(e)?e:e[sn]}function E_(n){return(n[ee]&4)===4}function wc(n){return(n[ee]&128)===128}function D_(n){return Ut(n[Xe])}function Sn(n,t){return t==null?null:n[t]}function ng(n){n[ai]=0}function og(n){n[ee]&1024||(n[ee]|=1024,wc(n)&&ui(n))}function I_(n,t){for(;n>0;)t=t[Po],n--;return t}function ma(n){return!!(n[ee]&9216||n[Tt]?.dirty)}function Sc(n){n[cn].changeDetectionScheduler?.notify(8),n[ee]&64&&(n[ee]|=1024),ma(n)&&ui(n)}function ui(n){n[cn].changeDetectionScheduler?.notify(0);let t=qn(n);for(;t!==null&&!(t[ee]&8192||(t[ee]|=8192,!wc(t)));)t=qn(t)}function kc(n,t){if(di(n))throw new z(911,!1);n[Un]===null&&(n[Un]=[]),n[Un].push(t)}function T_(n,t){if(n[Un]===null)return;let e=n[Un].indexOf(t);e!==-1&&n[Un].splice(e,1)}function qn(n){let t=n[Xe];return Ut(t)?t[Xe]:t}function ig(n){return n[er]??=[]}function rg(n){return n.cleanup??=[]}function A_(n,t,e,o){let i=ig(t);i.push(e),n.firstCreatePass&&rg(n).push(o,i.length-1)}var ce={lFrame:W_(null),bindingsEnabled:!0,skipHydrationRootTNode:null};var Tu=!1;function N_(){return ce.lFrame.elementDepthCount}function R_(){ce.lFrame.elementDepthCount++}function ag(){ce.lFrame.elementDepthCount--}function F_(){return ce.bindingsEnabled}function L_(){return ce.skipHydrationRootTNode!==null}function sg(n){return ce.skipHydrationRootTNode===n}function cg(){ce.skipHydrationRootTNode=null}function Z(){return ce.lFrame.lView}function Ne(){return ce.lFrame.tView}function v(n){return ce.lFrame.contextLView=n,n[ze]}function y(n){return ce.lFrame.contextLView=null,n}function tt(){let n=lg();for(;n!==null&&n.type===64;)n=n.parent;return n}function lg(){return ce.lFrame.currentTNode}function V_(){let n=ce.lFrame,t=n.currentTNode;return n.isParent?t:t.parent}function or(n,t){let e=ce.lFrame;e.currentTNode=n,e.isParent=t}function dg(){return ce.lFrame.isParent}function j_(){ce.lFrame.isParent=!1}function $_(){return ce.lFrame.contextLView}function ug(){return Tu}function oa(n){let t=Tu;return Tu=n,t}function ir(){let n=ce.lFrame,t=n.bindingRootIndex;return t===-1&&(t=n.bindingRootIndex=n.tView.bindingStartIndex),t}function gg(){return ce.lFrame.bindingIndex}function z_(n){return ce.lFrame.bindingIndex=n}function Zn(){return ce.lFrame.bindingIndex++}function _a(n){let t=ce.lFrame,e=t.bindingIndex;return t.bindingIndex=t.bindingIndex+n,e}function B_(){return ce.lFrame.inI18n}function H_(n,t){let e=ce.lFrame;e.bindingIndex=e.bindingRootIndex=n,Ec(t)}function U_(){return ce.lFrame.currentDirectiveIndex}function Ec(n){ce.lFrame.currentDirectiveIndex=n}function G_(n){let t=ce.lFrame.currentDirectiveIndex;return t===-1?null:n[t]}function pg(){return ce.lFrame.currentQueryIndex}function Dc(n){ce.lFrame.currentQueryIndex=n}function cv(n){let t=n[K];return t.type===2?t.declTNode:t.type===1?n[yt]:null}function fg(n,t,e){if(e&4){let i=t,r=n;for(;i=i.parent,i===null&&!(e&1);)if(i=cv(r),i===null||(r=r[Po],i.type&10))break;if(i===null)return!1;t=i,n=r}let o=ce.lFrame=q_();return o.currentTNode=t,o.lView=n,!0}function Ic(n){let t=q_(),e=n[K];ce.lFrame=t,t.currentTNode=e.firstChild,t.lView=n,t.tView=e,t.contextLView=n,t.bindingIndex=e.bindingStartIndex,t.inI18n=!1}function q_(){let n=ce.lFrame,t=n===null?null:n.child;return t===null?W_(n):t}function W_(n){let t={currentTNode:null,isParent:!0,lView:null,tView:null,selectedIndex:-1,contextLView:null,elementDepthCount:0,currentNamespace:null,currentDirectiveIndex:-1,bindingRootIndex:-1,bindingIndex:-1,currentQueryIndex:0,parent:n,child:null,inI18n:!1};return n!==null&&(n.child=t),t}function K_(){let n=ce.lFrame;return ce.lFrame=n.parent,n.currentTNode=null,n.lView=null,n}var mg=K_;function Tc(){let n=K_();n.isParent=!0,n.tView=null,n.selectedIndex=-1,n.contextLView=null,n.elementDepthCount=0,n.currentDirectiveIndex=-1,n.currentNamespace=null,n.bindingRootIndex=-1,n.bindingIndex=-1,n.currentQueryIndex=0}function Y_(n){return(ce.lFrame.contextLView=I_(n,ce.lFrame.contextLView))[ze]}function qt(){return ce.lFrame.selectedIndex}function vo(n){ce.lFrame.selectedIndex=n}function gi(){let n=ce.lFrame;return yc(n.tView,n.selectedIndex)}function yo(){ce.lFrame.currentNamespace=Qi}function rr(){lv()}function lv(){ce.lFrame.currentNamespace=null}function _g(){return ce.lFrame.currentNamespace}var Z_=!0;function Ac(){return Z_}function Nc(n){Z_=n}function Au(n,t=null,e=null,o){let i=hg(n,t,e,o);return i.resolveInjectorInitializers(),i}function hg(n,t=null,e=null,o,i=new Set){let r=[e||ht,P_(n)],a;return new ni(r,t||pa(),a||null,i)}var Ct=class n{static THROW_IF_NOT_FOUND=Xo;static NULL=new Zi;static create(t,e){if(Array.isArray(t))return Au({name:""},e,t,"");{let o=t.name??"";return Au({name:o},t.parent,t.providers,o)}}static \u0275prov=te({token:n,providedIn:"any",factory:()=>Q(ua)});static __NG_ELEMENT_ID__=-1},Ue=new B(""),Ae=class{static __NG_ELEMENT_ID__=dv;static __NG_ENV_ID__=t=>t},dc=class extends Ae{_lView;constructor(t){super(),this._lView=t}get destroyed(){return di(this._lView)}onDestroy(t){let e=this._lView;return kc(e,t),()=>T_(e,t)}};function dv(){return new dc(Z())}var Cg=!1,Q_=new B(""),kn=(()=>{class n{taskId=0;pendingTasks=new Set;destroyed=!1;pendingTask=new st(!1);debugTaskTracker=D(Q_,{optional:!0});get hasPendingTasks(){return this.destroyed?!1:this.pendingTask.value}get hasPendingTasksObservable(){return this.destroyed?new ie(e=>{e.next(!1),e.complete()}):this.pendingTask}add(){!this.hasPendingTasks&&!this.destroyed&&this.pendingTask.next(!0);let e=this.taskId++;return this.pendingTasks.add(e),this.debugTaskTracker?.add(e),e}has(e){return this.pendingTasks.has(e)}remove(e){this.pendingTasks.delete(e),this.debugTaskTracker?.remove(e),this.pendingTasks.size===0&&this.hasPendingTasks&&this.pendingTask.next(!1)}ngOnDestroy(){this.pendingTasks.clear(),this.hasPendingTasks&&this.pendingTask.next(!1),this.destroyed=!0,this.pendingTask.unsubscribe()}static \u0275prov=te({token:n,providedIn:"root",factory:()=>new n})}return n})(),Nu=class extends ye{__isAsync;destroyRef=void 0;pendingTasks=void 0;constructor(t=!1){super(),this.__isAsync=t,x_()&&(this.destroyRef=D(Ae,{optional:!0})??void 0,this.pendingTasks=D(kn,{optional:!0})??void 0)}emit(t){let e=Y(null);try{super.next(t)}finally{Y(e)}}subscribe(t,e,o){let i=t,r=e||(()=>null),a=o;if(t&&typeof t=="object"){let c=t;i=c.next?.bind(c),r=c.error?.bind(c),a=c.complete?.bind(c)}this.__isAsync&&(r=this.wrapInTimeout(r),i&&(i=this.wrapInTimeout(i)),a&&(a=this.wrapInTimeout(a)));let s=super.subscribe({next:i,error:r,complete:a});return t instanceof Be&&t.add(s),s}wrapInTimeout(t){return e=>{let o=this.pendingTasks?.add();setTimeout(()=>{try{t(e)}finally{o!==void 0&&this.pendingTasks?.remove(o)}})}}},Ke=Nu;function uc(...n){}function Mg(n){let t,e;function o(){n=uc;try{e!==void 0&&typeof cancelAnimationFrame=="function"&&cancelAnimationFrame(e),t!==void 0&&clearTimeout(t)}catch(i){}}return t=setTimeout(()=>{n(),o()}),typeof requestAnimationFrame=="function"&&(e=requestAnimationFrame(()=>{n(),o()})),()=>o()}function J_(n){return queueMicrotask(()=>n()),()=>{n=uc}}var bg="isAngularZone",ia=bg+"_ID",uv=0,ge=class n{hasPendingMacrotasks=!1;hasPendingMicrotasks=!1;isStable=!0;onUnstable=new Ke(!1);onMicrotaskEmpty=new Ke(!1);onStable=new Ke(!1);onError=new Ke(!1);constructor(t){let{enableLongStackTrace:e=!1,shouldCoalesceEventChangeDetection:o=!1,shouldCoalesceRunChangeDetection:i=!1,scheduleInRootZone:r=Cg}=t;if(typeof Zone>"u")throw new z(908,!1);Zone.assertZonePatched();let a=this;a._nesting=0,a._outer=a._inner=Zone.current,Zone.TaskTrackingZoneSpec&&(a._inner=a._inner.fork(new Zone.TaskTrackingZoneSpec)),e&&Zone.longStackTraceZoneSpec&&(a._inner=a._inner.fork(Zone.longStackTraceZoneSpec)),a.shouldCoalesceEventChangeDetection=!i&&o,a.shouldCoalesceRunChangeDetection=i,a.callbackScheduled=!1,a.scheduleInRootZone=r,fv(a)}static isInAngularZone(){return typeof Zone<"u"&&Zone.current.get(bg)===!0}static assertInAngularZone(){if(!n.isInAngularZone())throw new z(909,!1)}static assertNotInAngularZone(){if(n.isInAngularZone())throw new z(909,!1)}run(t,e,o){return this._inner.run(t,e,o)}runTask(t,e,o,i){let r=this._inner,a=r.scheduleEventTask("NgZoneEvent: "+i,t,gv,uc,uc);try{return r.runTask(a,e,o)}finally{r.cancelTask(a)}}runGuarded(t,e,o){return this._inner.runGuarded(t,e,o)}runOutsideAngular(t){return this._outer.run(t)}},gv={};function Pg(n){if(n._nesting==0&&!n.hasPendingMicrotasks&&!n.isStable)try{n._nesting++,n.onMicrotaskEmpty.emit(null)}finally{if(n._nesting--,!n.hasPendingMicrotasks)try{n.runOutsideAngular(()=>n.onStable.emit(null))}finally{n.isStable=!0}}}function pv(n){if(n.isCheckStableRunning||n.callbackScheduled)return;n.callbackScheduled=!0;function t(){Mg(()=>{n.callbackScheduled=!1,Ru(n),n.isCheckStableRunning=!0,Pg(n),n.isCheckStableRunning=!1})}n.scheduleInRootZone?Zone.root.run(()=>{t()}):n._outer.run(()=>{t()}),Ru(n)}function fv(n){let t=()=>{pv(n)},e=uv++;n._inner=n._inner.fork({name:"angular",properties:{[bg]:!0,[ia]:e,[ia+e]:!0},onInvokeTask:(o,i,r,a,s,c)=>{if(mv(c))return o.invokeTask(r,a,s,c);try{return d_(n),o.invokeTask(r,a,s,c)}finally{(n.shouldCoalesceEventChangeDetection&&a.type==="eventTask"||n.shouldCoalesceRunChangeDetection)&&t(),u_(n)}},onInvoke:(o,i,r,a,s,c,p)=>{try{return d_(n),o.invoke(r,a,s,c,p)}finally{n.shouldCoalesceRunChangeDetection&&!n.callbackScheduled&&!_v(c)&&t(),u_(n)}},onHasTask:(o,i,r,a)=>{o.hasTask(r,a),i===r&&(a.change=="microTask"?(n._hasPendingMicrotasks=a.microTask,Ru(n),Pg(n)):a.change=="macroTask"&&(n.hasPendingMacrotasks=a.macroTask))},onHandleError:(o,i,r,a)=>(o.handleError(r,a),n.runOutsideAngular(()=>n.onError.emit(a)),!1)})}function Ru(n){n._hasPendingMicrotasks||(n.shouldCoalesceEventChangeDetection||n.shouldCoalesceRunChangeDetection)&&n.callbackScheduled===!0?n.hasPendingMicrotasks=!0:n.hasPendingMicrotasks=!1}function d_(n){n._nesting++,n.isStable&&(n.isStable=!1,n.onUnstable.emit(null))}function u_(n){n._nesting--,Pg(n)}var ra=class{hasPendingMicrotasks=!1;hasPendingMacrotasks=!1;isStable=!0;onUnstable=new Ke;onMicrotaskEmpty=new Ke;onStable=new Ke;onError=new Ke;run(t,e,o){return t.apply(e,o)}runGuarded(t,e,o){return t.apply(e,o)}runOutsideAngular(t){return t()}runTask(t,e,o,i){return t.apply(e,o)}};function mv(n){return X_(n,"__ignore_ng_zone__")}function _v(n){return X_(n,"__scheduler_tick__")}function X_(n,t){return!Array.isArray(n)||n.length!==1?!1:n[0]?.data?.[t]===!0}var nn=class{_console=console;handleError(t){this._console.error("ERROR",t)}},Nt=new B("",{factory:()=>{let n=D(ge),t=D(Le),e;return o=>{n.runOutsideAngular(()=>{t.destroyed&&!e?setTimeout(()=>{throw o}):(e??=t.get(nn),e.handleError(o))})}}}),eh={provide:ri,useValue:()=>{let n=D(nn,{optional:!0})},multi:!0};function x(n,t){let[e,o,i]=iu(n,t?.equal),r=e,a=r[nt];return r.set=o,r.update=i,r.asReadonly=Rc.bind(r),r}function Rc(){let n=this[nt];if(n.readonlyFn===void 0){let t=()=>this();t[nt]=n,n.readonlyFn=t}return n.readonlyFn}var ha=new B("",{factory:()=>hv}),hv="ng";var Fc=new B(""),Ca=new B("",{providedIn:"platform",factory:()=>"unknown"});var Ma=new B("",{factory:()=>D(Ue).body?.querySelector("[ngCspNonce]")?.getAttribute("ngCspNonce")||null});var ba=(()=>{class n{view;node;constructor(e,o){this.view=e,this.node=o}static __NG_ELEMENT_ID__=Cv}return n})();function Cv(){return new ba(Z(),tt())}var bn=class{},ar=new B("",{factory:()=>!0});var Lc=new B(""),Vc=(()=>{class n{static \u0275prov=te({token:n,providedIn:"root",factory:()=>new Fu})}return n})(),Fu=class{dirtyEffectCount=0;queues=new Map;add(t){this.enqueue(t),this.schedule(t)}schedule(t){t.dirty&&this.dirtyEffectCount++}remove(t){let e=t.zone,o=this.queues.get(e);o.has(t)&&(o.delete(t),t.dirty&&this.dirtyEffectCount--)}enqueue(t){let e=t.zone;this.queues.has(e)||this.queues.set(e,new Set);let o=this.queues.get(e);o.has(t)||o.add(t)}flush(){for(;this.dirtyEffectCount>0;){let t=!1;for(let[e,o]of this.queues)e===null?t||=this.flushQueue(o):t||=e.run(()=>this.flushQueue(o));t||(this.dirtyEffectCount=0)}}flushQueue(t){let e=!1;for(let o of t)o.dirty&&(this.dirtyEffectCount--,e=!0,o.run());return e}},gc=class{[nt];constructor(t){this[nt]=t}destroy(){this[nt].destroy()}};function Ye(n,t){let e=t?.injector??D(Ct),o=t?.manualCleanup!==!0?e.get(Ae):null,i,r=e.get(ba,null,{optional:!0}),a=e.get(bn);return r!==null?(i=Pv(r.view,a,n),o instanceof dc&&o._lView===r.view&&(o=null)):i=Ov(n,e.get(Vc),a),i.injector=e,o!==null&&(i.onDestroyFns=[o.onDestroy(()=>i.destroy())]),new gc(i)}var th=F(S({},ru),{cleanupFns:void 0,zone:null,onDestroyFns:null,run(){let n=oa(!1);try{au(this)}finally{oa(n)}},cleanup(){if(!this.cleanupFns?.length)return;let n=Y(null);try{for(;this.cleanupFns.length;)this.cleanupFns.pop()()}finally{this.cleanupFns=[],Y(n)}}}),Mv=F(S({},th),{consumerMarkedDirty(){this.scheduler.schedule(this),this.notifier.notify(12)},destroy(){if(qo(this),this.onDestroyFns!==null)for(let n of this.onDestroyFns)n();this.cleanup(),this.scheduler.remove(this)}}),bv=F(S({},th),{consumerMarkedDirty(){this.view[ee]|=8192,ui(this.view),this.notifier.notify(13)},destroy(){if(qo(this),this.onDestroyFns!==null)for(let n of this.onDestroyFns)n();this.cleanup(),this.view[Gn]?.delete(this)}});function Pv(n,t,e){let o=Object.create(bv);return o.view=n,o.zone=typeof Zone<"u"?Zone.current:null,o.notifier=t,o.fn=nh(o,e),n[Gn]??=new Set,n[Gn].add(o),o.consumerMarkedDirty(o),o}function Ov(n,t,e){let o=Object.create(Mv);return o.fn=nh(o,n),o.scheduler=t,o.notifier=e,o.zone=typeof Zone<"u"?Zone.current:null,o.scheduler.add(o),o.notifier.notify(12),o}function nh(n,t){return()=>{t(e=>(n.cleanupFns??=[]).push(e))}}function Pa(n){return typeof n=="function"&&n[nt]!==void 0}function jc(n){return Pa(n)&&typeof n.set=="function"}var Oa=(()=>{class n{internalPendingTasks=D(kn);scheduler=D(bn);errorHandler=D(Nt);add(){let e=this.internalPendingTasks.add();return()=>{this.internalPendingTasks.has(e)&&(this.scheduler.notify(11),this.internalPendingTasks.remove(e))}}run(e){let o=this.add();try{e().catch(this.errorHandler).finally(o)}catch(i){this.errorHandler(i),o()}}static \u0275prov=te({token:n,providedIn:"root",factory:()=>new n})}return n})();function Na(n){return{toString:n}.toString()}var pe=(function(n){return n[n.TemplateCreateStart=0]="TemplateCreateStart",n[n.TemplateCreateEnd=1]="TemplateCreateEnd",n[n.TemplateUpdateStart=2]="TemplateUpdateStart",n[n.TemplateUpdateEnd=3]="TemplateUpdateEnd",n[n.LifecycleHookStart=4]="LifecycleHookStart",n[n.LifecycleHookEnd=5]="LifecycleHookEnd",n[n.OutputStart=6]="OutputStart",n[n.OutputEnd=7]="OutputEnd",n[n.BootstrapApplicationStart=8]="BootstrapApplicationStart",n[n.BootstrapApplicationEnd=9]="BootstrapApplicationEnd",n[n.BootstrapComponentStart=10]="BootstrapComponentStart",n[n.BootstrapComponentEnd=11]="BootstrapComponentEnd",n[n.ChangeDetectionStart=12]="ChangeDetectionStart",n[n.ChangeDetectionEnd=13]="ChangeDetectionEnd",n[n.ChangeDetectionSyncStart=14]="ChangeDetectionSyncStart",n[n.ChangeDetectionSyncEnd=15]="ChangeDetectionSyncEnd",n[n.AfterRenderHooksStart=16]="AfterRenderHooksStart",n[n.AfterRenderHooksEnd=17]="AfterRenderHooksEnd",n[n.ComponentStart=18]="ComponentStart",n[n.ComponentEnd=19]="ComponentEnd",n[n.DeferBlockStateStart=20]="DeferBlockStateStart",n[n.DeferBlockStateEnd=21]="DeferBlockStateEnd",n[n.DynamicComponentStart=22]="DynamicComponentStart",n[n.DynamicComponentEnd=23]="DynamicComponentEnd",n[n.HostBindingsUpdateStart=24]="HostBindingsUpdateStart",n[n.HostBindingsUpdateEnd=25]="HostBindingsUpdateEnd",n})(pe||{}),Kc=class{previousValue;currentValue;firstChange;constructor(t,e,o){this.previousValue=t,this.currentValue=e,this.firstChange=o}isFirstChange(){return this.firstChange}};function Hh(n,t,e,o){t!==null?t.applyValueToInputSignal(t,o):n[e]=o}var Uh=null,In=(()=>{Uh=oh;let n=()=>oh;return n.ngInherit=!0,n})();function Iv(){return Uh}function oh(n){return n.type.prototype.ngOnChanges&&(n.setInput=Av),Tv}function Tv(){let n=Gh(this),t=n?.current;if(t){let e=n.previous;if(e===Mo)n.previous=t;else for(let o in t)e[o]=t[o];n.current=null,this.ngOnChanges(t)}}function Av(n,t,e,o,i){let r=this.declaredInputs[o],a=Gh(n)||Nv(n,{previous:Mo,current:null}),s=a.current||(a.current={}),c=a.previous,p=c[r];s[r]=new Kc(p&&p.currentValue,e,c===Mo),Hh(n,t,i,e)}var Ag="__ngSimpleChanges__";function Gh(n){return Object.hasOwn(n,Ag)&&n[Ag]||null}function Nv(n,t){return n[Ag]=t}var ih=[];var Ce=function(n,t=null,e){for(let o=0;o=o)break}else t[c]<0&&(n[ai]+=65536),(s>14>16&&(n[ee]&3)===t&&(n[ee]+=16384,rh(s,r)):rh(s,r)}var cr=-1,mi=class{factory;name;injectImpl;resolving=!1;canSeeViewProviders;multi;componentProviders;index;providerFactory;constructor(t,e,o,i){this.factory=t,this.name=i,this.canSeeViewProviders=e,this.injectImpl=o}};function Vv(n){return(n.flags&8)!==0}function jv(n){return(n.flags&16)!==0}function $v(n,t,e){let o=0;for(;ot){a=r-1;break}}}for(;r>16}function Zc(n,t){let e=Bv(n),o=t;for(;e>0;)o=o[Po],e--;return o}var Ng=!0;function Qc(n){let t=Ng;return Ng=n,t}var Hv=256,Yh=Hv-1,Zh=5,Uv=0,En={};function Gv(n,t,e){let o;typeof e=="string"?o=e.charCodeAt(0)||0:Object.hasOwn(e,oi)&&(o=e[oi]),o==null&&(o=e[oi]=Uv++);let i=o&Yh,r=1<>Zh)]|=r}function Jc(n,t){let e=Qh(n,t);if(e!==-1)return e;let o=t[K];o.firstCreatePass&&(n.injectorIndex=t.length,vg(o.data,n),vg(t,null),vg(o.blueprint,null));let i=dp(n,t),r=n.injectorIndex;if(Kh(i)){let a=Yc(i),s=Zc(i,t),c=s[K].data;for(let p=0;p<8;p++)t[r+p]=s[a+p]|c[a+p]}return t[r+8]=i,r}function vg(n,t){n.push(0,0,0,0,0,0,0,0,t)}function Qh(n,t){return n.injectorIndex===-1||n.parent&&n.parent.injectorIndex===n.injectorIndex||t[n.injectorIndex+8]===null?-1:n.injectorIndex}function dp(n,t){if(n.parent&&n.parent.injectorIndex!==-1)return n.parent.injectorIndex;let e=0,o=null,i=t;for(;i!==null;){if(o=nC(i),o===null)return cr;if(e++,i=i[Po],o.injectorIndex!==-1)return o.injectorIndex|e<<16}return cr}function Rg(n,t,e){Gv(n,t,e)}function qv(n,t){if(t==="class")return n.classes;if(t==="style")return n.styles;let e=n.attrs;if(e){let o=e.length,i=0;for(;i>20,_=o?s:s+f,C=i?s+f:p;for(let h=_;h=c&&P.type===e)return h}if(i){let h=a[c];if(h&&wn(h)&&h.type===e)return c}return null}function Sa(n,t,e,o,i){let r=n[e],a=t.data;if(r instanceof mi){let s=r;if(s.resolving)throw Hu("");let c=Qc(s.canSeeViewProviders);s.resolving=!0;let p=a[e].type||a[e],f,_=s.injectImpl?_t(s.injectImpl):null,C=fg(n,o,0);try{r=n[e]=s.factory(void 0,i,a,n,o),t.firstCreatePass&&e>=o.directiveStart&&Rv(e,a[e],t)}finally{_!==null&&_t(_),Qc(c),s.resolving=!1,mg()}}return r}function Kv(n){if(typeof n=="string")return n.charCodeAt(0)||0;let t=Object.hasOwn(n,oi)?n[oi]:void 0;return typeof t=="number"?t>=0?t&Yh:Yv:t}function sh(n,t,e){let o=1<>Zh)]&o)}function ch(n,t){return!(n&2)&&!(n&1&&t)}var xo=class{_tNode;_lView;constructor(t,e){this._tNode=t,this._lView=e}get(t,e,o){return eC(this._tNode,this._lView,t,ei(o),e)}};function Yv(){return new xo(tt(),Z())}function ko(n){return Na(()=>{let t=n.prototype.constructor,e=t[na]||Fg(t),o=Object.prototype,i=Object.getPrototypeOf(n.prototype).constructor;for(;i&&i!==o;){let r=i[na]||Fg(i);if(r&&r!==e)return r;i=Object.getPrototypeOf(i)}return r=>new r})}function Fg(n){return Lu(n)?()=>{let t=Fg(lt(n));return t&&t()}:Co(n)}function Zv(n,t,e,o,i){let r=n,a=t;for(;r!==null&&a!==null&&a[ee]&2048&&!nr(a);){let s=tC(r,a,e,o|2,En);if(s!==En)return s;let c=r.parent;if(!c){let p=a[Qu];if(p){let f=p.get(e,En,o&-5);if(f!==En)return f}c=nC(a),a=a[Po]}r=c}return i}function nC(n){let t=n[K],e=t.type;return e===2?t.declTNode:e===1?n[yt]:null}function Ra(n){return qv(tt(),n)}function Oe(n){return{token:n.token,providedIn:n.autoProvided===!1?null:"root",factory:n.factory,value:void 0}}function Qv(){return pr(tt(),Z())}function pr(n,t){return new Mt(At(n,t))}var Mt=(()=>{class n{nativeElement;constructor(e){this.nativeElement=e}static __NG_ELEMENT_ID__=Qv}return n})();function Jv(n){return n instanceof Mt?n.nativeElement:n}function Xv(){return this._results[Symbol.iterator]()}var Xc=class{_emitDistinctChangesOnly;dirty=!0;_onDirty=void 0;_results=[];_changesDetected=!1;_changes=void 0;length=0;first=void 0;last=void 0;get changes(){return this._changes??=new ye}constructor(t=!1){this._emitDistinctChangesOnly=t}get(t){return this._results[t]}map(t){return this._results.map(t)}filter(t){return this._results.filter(t)}find(t){return this._results.find(t)}reduce(t,e){return this._results.reduce(t,e)}forEach(t){this._results.forEach(t)}some(t){return this._results.some(t)}toArray(){return this._results.slice()}toString(){return this._results.toString()}reset(t,e){this.dirty=!1;let o=M_(t);(this._changesDetected=!C_(this._results,o,e))&&(this._results=o,this.length=o.length,this.last=o[this.length-1],this.first=o[0])}notifyOnChanges(){this._changes!==void 0&&(this._changesDetected||!this._emitDistinctChangesOnly)&&this._changes.next(this)}onDirty(t){this._onDirty=t}setDirty(){this.dirty=!0,this._onDirty?.()}destroy(){this._changes!==void 0&&(this._changes.complete(),this._changes.unsubscribe())}[Symbol.iterator]=Xv};function oC(n){return(n.flags&128)===128}var up=(function(n){return n[n.OnPush=0]="OnPush",n[n.Eager=1]="Eager",n[n.Default=1]="Default",n})(up||{}),iC=new Map,ey=0;function ty(){return ey++}function ny(n){iC.set(n[yn],n)}function Lg(n){iC.delete(n[yn])}var lh="__ngContext__";function dr(n,t){Kn(t)?(n[lh]=t[yn],ny(t)):n[lh]=t}function rC(n){return sC(n[tr])}function aC(n){return sC(n[Ht])}function sC(n){for(;n!==null&&!Ut(n);)n=n[Ht];return n}var Vg;function gp(n){Vg=n}function pp(){if(Vg!==void 0)return Vg;if(typeof document<"u")return document;throw new z(210,!1)}var cC="r";var lC="di";var dC=!1,uC=new B("",{factory:()=>dC});var dh=new WeakMap;function oy(n,t){if(n==null||typeof n!="object")return;let e=dh.get(n);e||(e=new WeakSet,dh.set(n,e)),e.add(t)}var iy=(n,t,e,o)=>{};function ry(n,t,e,o){iy(n,t,e,o)}function fp(n){return(n.flags&32)===32}var ay=()=>null;function gC(n,t,e=!1){return ay(n,t,e)}function pC(n,t){let e=n.contentQueries;if(e!==null){let o=Y(null);try{for(let i=0;in,createScript:n=>n,createScriptURL:n=>n})}catch(n){}return $c}function fl(n){return cy()?.createHTML(n)||n}var zc;function mC(){if(zc===void 0&&(zc=null,an.trustedTypes))try{zc=an.trustedTypes.createPolicy("angular#unsafe-bypass",{createHTML:n=>n,createScript:n=>n,createScriptURL:n=>n})}catch(n){}return zc}function uh(n){return mC()?.createHTML(n)||n}function gh(n){return mC()?.createScriptURL(n)||n}var el=class{changingThisBreaksApplicationSecurity;constructor(t){this.changingThisBreaksApplicationSecurity=t}toString(){return`SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity} (see ${pc})`}};function _i(n){return n instanceof el?n.changingThisBreaksApplicationSecurity:n}function ml(n,t){let e=_C(n);if(e!=null&&e!==t){if(e==="ResourceURL"&&t==="URL")return!0;throw new Error(`Required a safe ${t}, got a ${e} (see ${pc})`)}return e===t}function _C(n){return n instanceof el&&n.getTypeName()||null}function ly(n){let t=new zg(n);return dy()?new $g(t):t}var $g=class{inertDocumentHelper;constructor(t){this.inertDocumentHelper=t}getInertBodyElement(t){t=""+t;try{let e=new window.DOMParser().parseFromString(fl(t),"text/html").body;return e===null?this.inertDocumentHelper.getInertBodyElement(t):(e.firstChild?.remove(),e)}catch(e){return null}}},zg=class{defaultDoc;inertDocument;constructor(t){this.defaultDoc=t,this.inertDocument=this.defaultDoc.implementation.createHTMLDocument("sanitization-inert")}getInertBodyElement(t){let e=this.inertDocument.createElement("template");return e.innerHTML=fl(t),e}};function dy(){try{return!!new window.DOMParser().parseFromString(fl(""),"text/html")}catch(n){return!1}}var uy=/^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;function mp(n){return n=String(n),n.match(uy)?n:"unsafe:"+n}function Qn(n){let t=Object.create(null);for(let e of n.split(","))t[e]=!0;return t}function Fa(...n){let t=Object.create(null);for(let e of n)for(let o in e)Object.hasOwn(e,o)&&(t[o]=!0);return t}var hC=Qn("area,br,col,hr,img,wbr"),CC=Qn("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),MC=Qn("rp,rt"),gy=Fa(MC,CC),py=Fa(CC,Qn("address,article,aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul")),fy=Fa(MC,Qn("a,abbr,acronym,audio,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video")),ph=Fa(hC,py,fy,gy),bC=Qn("background,cite,href,itemtype,longdesc,poster,src,xlink:href"),my=Qn("abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,scope,scrolling,shape,size,sizes,span,srclang,srcset,start,summary,tabindex,target,title,translate,type,usemap,valign,value,vspace,width"),_y=Qn("aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,aria-colspan,aria-controls,aria-current,aria-describedby,aria-details,aria-disabled,aria-dropeffect,aria-errormessage,aria-expanded,aria-flowto,aria-grabbed,aria-haspopup,aria-hidden,aria-invalid,aria-keyshortcuts,aria-label,aria-labelledby,aria-level,aria-live,aria-modal,aria-multiline,aria-multiselectable,aria-orientation,aria-owns,aria-placeholder,aria-posinset,aria-pressed,aria-readonly,aria-relevant,aria-required,aria-roledescription,aria-rowcount,aria-rowindex,aria-rowspan,aria-selected,aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext"),hy=Fa(bC,my,_y),Cy=Qn("script,style,template"),Bg=class{sanitizedSomething=!1;buf=[];sanitizeChildren(t){let e=t.firstChild,o=!0,i=[];for(;e;){if(e.nodeType===Node.ELEMENT_NODE?o=this.startElement(e):e.nodeType===Node.TEXT_NODE?this.chars(e.nodeValue):this.sanitizedSomething=!0,o&&e.firstChild){i.push(e),e=Py(e);continue}for(;e;){e.nodeType===Node.ELEMENT_NODE&&this.endElement(e);let r=by(e);if(r){e=r;break}e=i.pop()}}return this.buf.join("")}startElement(t){let e=fh(t).toLowerCase();if(!Object.hasOwn(ph,e))return this.sanitizedSomething=!0,!Object.hasOwn(Cy,e);this.buf.push("<"),this.buf.push(e);let o=t.attributes;for(let i=0;i"),!0}endElement(t){let e=fh(t).toLowerCase();Object.hasOwn(ph,e)&&!Object.hasOwn(hC,e)&&(this.buf.push(""))}chars(t){this.buf.push(mh(t))}};function My(n,t){return(n.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_CONTAINED_BY)!==Node.DOCUMENT_POSITION_CONTAINED_BY}function by(n){let t=n.nextSibling;if(t&&n!==t.previousSibling)throw PC(t);return t}function Py(n){let t=n.firstChild;if(t&&My(n,t))throw PC(t);return t}function fh(n){let t=n.nodeName;return typeof t=="string"?t:"FORM"}function PC(n){return new Error(`Failed to sanitize html because the element is clobbered: ${n.outerHTML}`)}var Oy=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,vy=/([^\#-~ |!])/g;function mh(n){return n.replace(/&/g,"&").replace(Oy,function(t){let e=t.charCodeAt(0),o=t.charCodeAt(1);return"&#"+((e-55296)*1024+(o-56320)+65536)+";"}).replace(vy,function(t){return"&#"+t.charCodeAt(0)+";"}).replace(//g,">")}var Bc;function OC(n,t){let e=null;try{Bc=Bc||ly(n);let o=t?String(t):"";e=Bc.getInertBodyElement(o);let i=5,r=o;do{if(i===0)throw new Error("Failed to sanitize html because the input is unstable");i--,o=r,r=e.innerHTML,e=Bc.getInertBodyElement(o)}while(o!==r);let s=new Bg().sanitizeChildren(_h(e)||e);return fl(s)}finally{if(e){let o=_h(e)||e;for(;o.firstChild;)o.firstChild.remove()}}}function _h(n){return"content"in n&&yy(n)?n.content:null}function yy(n){return n.nodeType===Node.ELEMENT_NODE&&n.nodeName==="TEMPLATE"}function xy(n,t){return n.createText(t)}function wy(n,t,e){n.setValue(t,e)}function vC(n,t,e){return n.createElement(t,e)}function pi(n,t,e,o,i){n.insertBefore(t,e,o,i)}function yC(n,t,e){n.appendChild(t,e)}function hh(n,t,e,o,i){o!==null?pi(n,t,e,o,i):yC(n,t,e)}function xC(n,t,e,o){n.removeChild(null,t,e,o)}function Sy(n,t,e){n.setAttribute(t,"style",e)}function ky(n,t,e){e===""?n.removeAttribute(t,"class"):n.setAttribute(t,"class",e)}function wC(n,t,e){let{mergedAttrs:o,classes:i,styles:r}=e;o!==null&&$v(n,t,o),i!==null&&ky(n,t,i),r!==null&&Sy(n,t,r)}function Ey(n,t=!0){if(n[0]!=":")return[null,n];let e=n.indexOf(":",1);if(e===-1){if(t)throw new Error(`Unsupported format "${n}" expecting ":namespace:name"`);return[null,n]}return[n.slice(1,e),n.slice(e+1)]}function Pi(n,t,e){if(t!==void 0&&e!==void 0&&kC(t,e)!==Je.HTML)return n;let o=hp();return o?uh(o.sanitize(Je.HTML,n)||""):ml(n,"HTML")?uh(_i(n)):OC(pp(),rn(n))}function kt(n){let t=hp();return t?t.sanitize(Je.URL,n)||"":ml(n,"URL")?_i(n):mp(rn(n))}function SC(n){let t=hp();if(t)return gh(t.sanitize(Je.RESOURCE_URL,n)||"");if(ml(n,"ResourceURL"))return gh(_i(n));throw new z(904,!1)}function Dy(n,t){switch(kC(n,t)){case Je.RESOURCE_URL:return SC;case Je.URL:return kt;default:return null}}function _p(n,t,e){return Dy(t,e)?.(n)??n}function hp(){let n=Z();return n&&n[cn].sanitizer}function kC(n,t){let[e,o]=Iy(n);return k_(o,t,e)}function Iy(n){n=n.toLowerCase();let t=Ey(n,!1);if(t[0])return t;let o=qt()===-1?null:gi(),i=o?.namespace;if(n==="#host"&&o?.type===2){let r=At(o,Z());if(r.tagName&&(n=r.tagName.toLowerCase()),i==null){let a=r.namespaceURI;i=a&&sy[a]}}return[i,n]}function _l(n){return n.ownerDocument.defaultView}function hl(n){return n.ownerDocument}function Ty(n){return n instanceof Function?n():n}function Ay(n,t,e){let o=n.length;for(;;){let i=n.indexOf(t,e);if(i===-1)return i;if(i===0||n.charCodeAt(i-1)<=32){let r=t.length;if(i+r===o||n.charCodeAt(i+r)<=32)return i}e=i+1}}var EC="ng-template";function Ny(n,t,e,o){let i=0;if(o){for(;i-1){let r;for(;++ir?_="":_=i[f+1].toLowerCase(),o&2&&p!==_){if(ln(o))return!1;a=!0}}}}return ln(o)||a}function ln(n){return(n&1)===0}function Ly(n,t,e,o){if(t===null)return-1;let i=0;if(o||!e){let r=!1;for(;i-1)for(e++;e0?'="'+s+'"':"")+"]"}else o&8?i+="."+a:o&4&&(i+=" "+a);else i!==""&&!ln(a)&&(t+=Ch(r,i),i=""),o=a,r=r||!ln(o);e++}return i!==""&&(t+=Ch(r,i)),t}function By(n){return n.map(zy).join(",")}function Hy(n){let t=[],e=[],o=1,i=2;for(;o=0;s--){let{el:c,declarationView:p}=o[s],f=c.parentNode;c===t?(o.splice(s,1),ya.add(c),c.dispatchEvent(new CustomEvent("animationend",{detail:{cancel:!0}}))):r&&c===r?(o.splice(s,1),c.dispatchEvent(new CustomEvent("animationend",{detail:{cancel:!0}})),c.parentNode?.removeChild(c)):f&&i&&f!==i&&(a===null||p===null||a===p)&&(o.splice(s,1),c.dispatchEvent(new CustomEvent("animationend",{detail:{cancel:!0}})),c.parentNode?.removeChild(c))}}function qy(n,t,e){let o=DC(e),i=Hg.get(n);i?i.some(r=>r.el===t)||i.push({el:t,declarationView:o}):Hg.set(n,[{el:t,declarationView:o}])}var Cl=(function(n){return n[n.CHANGE_DETECTION=0]="CHANGE_DETECTION",n[n.AFTER_NEXT_RENDER=1]="AFTER_NEXT_RENDER",n})(Cl||{}),Tn=new B(""),Mh=new Set;function An(n){Mh.has(n)||(Mh.add(n),performance?.mark?.("mark_feature_usage",{detail:{feature:n}}))}var bp=(()=>{class n{impl=null;execute(){this.impl?.execute()}static \u0275prov=te({token:n,providedIn:"root",factory:()=>new n})}return n})(),IC=[0,1,2,3],TC=(()=>{class n{ngZone=D(ge);scheduler=D(bn);errorHandler=D(nn,{optional:!0});sequences=new Set;deferredRegistrations=new Set;executing=!1;constructor(){D(Tn,{optional:!0})}execute(){let e=this.sequences.size>0;e&&Ce(pe.AfterRenderHooksStart),this.executing=!0;for(let o of IC)for(let i of this.sequences)if(!(i.erroredOrDestroyed||!i.hooks[o]))try{i.pipelinedValue=this.ngZone.runOutsideAngular(()=>this.maybeTrace(()=>{let r=i.hooks[o];return r(i.pipelinedValue)},i.snapshot))}catch(r){i.erroredOrDestroyed=!0,this.errorHandler?.handleError(r)}this.executing=!1;for(let o of this.sequences)o.afterRun(),o.once&&(this.sequences.delete(o),o.destroy());for(let o of this.deferredRegistrations)this.sequences.add(o);this.deferredRegistrations.size>0&&this.scheduler.notify(7),this.deferredRegistrations.clear(),e&&Ce(pe.AfterRenderHooksEnd)}register(e){let{view:o}=e;o!==void 0?((o[si]??=[]).push(e),ui(o),o[ee]|=8192):this.executing?this.deferredRegistrations.add(e):this.addSequence(e)}addSequence(e){this.sequences.add(e),this.scheduler.notify(7)}unregister(e){this.executing&&this.sequences.has(e)?(e.erroredOrDestroyed=!0,e.pipelinedValue=void 0,e.once=!0):(this.sequences.delete(e),this.deferredRegistrations.delete(e))}maybeTrace(e,o){return o?o.run(Cl.AFTER_NEXT_RENDER,e):e()}static \u0275prov=te({token:n,providedIn:"root",factory:()=>new n})}return n})(),tl=class{impl;hooks;view;once;snapshot;erroredOrDestroyed=!1;pipelinedValue=void 0;unregisterOnDestroy;constructor(t,e,o,i,r,a=null){this.impl=t,this.hooks=e,this.view=o,this.once=i,this.snapshot=a,this.unregisterOnDestroy=r?.onDestroy(()=>this.destroy())}afterRun(){this.erroredOrDestroyed=!1,this.pipelinedValue=void 0,this.snapshot?.dispose(),this.snapshot=null}destroy(){this.impl.unregister(this),this.unregisterOnDestroy?.();let t=this.view?.[si];t&&(this.view[si]=t.filter(e=>e!==this))}};function La(n,t){let e=t?.injector??D(Ct);return An("NgAfterNextRender"),Ky(n,e,t,!0)}function Wy(n){return n instanceof Function?[void 0,void 0,n,void 0]:[n.earlyRead,n.write,n.mixedReadWrite,n.read]}function Ky(n,t,e,o){let i=t.get(bp);i.impl??=t.get(TC);let r=t.get(Tn,null,{optional:!0}),a=e?.manualCleanup!==!0?t.get(Ae):null,s=t.get(ba,null,{optional:!0}),c=new tl(i.impl,Wy(n),s?.view,o,a,r?.snapshot(null));return i.impl.register(c),c}var Pp=new B("",{factory:()=>{let n=D(Le),t=new Set;return n.onDestroy(()=>t.clear()),{queue:t,isScheduled:!1,scheduler:null,injector:n}}});function AC(n,t,e){let o=n.get(Pp);if(Array.isArray(t))for(let i of t)o.queue.add(i),e?.detachedLeaveAnimationFns?.push(i);else o.queue.add(t),e?.detachedLeaveAnimationFns?.push(t);o.scheduler&&o.scheduler(n)}function Yy(n,t){let e=n.get(Pp);if(Array.isArray(t))for(let o of t)e.queue.delete(o);else e.queue.delete(t)}function Zy(n,t){let e=n.get(Pp);if(t.detachedLeaveAnimationFns){for(let o of t.detachedLeaveAnimationFns)e.queue.delete(o);t.detachedLeaveAnimationFns=void 0}}function Qy(n,t){for(let[e,o]of t)AC(n,o.animateFns)}function bh(n,t,e,o){let i=n?.[xn]?.enter;t!==null&&i&&i.has(e.index)&&Qy(o,i)}function Ph(n,t,e,o){try{e.get(ua)}catch(a){return o(!1)}let i=n?.[xn];i?.enter?.has(t.index)&&Yy(e,i.enter.get(t.index).animateFns);let r=Jy(n,t,i);if(r.size===0){let a=!1;if(n){let s=[];Ml(n,t,s),a=s.length>0}if(!a)return o(!1)}n&&wo.add(n[yn]),AC(e,()=>Xy(n,t,i||void 0,r,o),i||void 0)}function Jy(n,t,e){let o=new Map,i=e?.leave;if(i&&i.has(t.index)&&o.set(t.index,i.get(t.index)),n&&i)for(let[r,a]of i){if(o.has(r))continue;let c=n[K].data[r].parent;for(;c;){if(c===t){o.set(r,a);break}c=c.parent}}return o}function Xy(n,t,e,o,i){let r=[];if(e&&e.leave)for(let[a]of o){if(!e.leave.has(a))continue;let s=e.leave.get(a);for(let c of s.animateFns){let{promise:p}=c();r.push(p)}e.detachedLeaveAnimationFns=void 0}if(n&&Ml(n,t,r),r.length>0){let a=e||n?.[xn];if(a){let s=a.running;s&&r.push(s),a.running=Promise.allSettled(r),tx(n,a.running,i)}else Promise.allSettled(r).then(()=>{n&&wo.delete(n[yn]),i(!0)})}else n&&wo.delete(n[yn]),i(!1)}function Ml(n,t,e){if(t.type&12){let i=n[t.index];if(Ut(i))for(let r=Ve;r{n[xn]?.running===t&&(n[xn].running=void 0,wo.delete(n[yn])),e(!0)})}function sr(n,t,e,o,i,r,a,s){if(i!=null){let c,p=!1;Ut(i)?c=i:Kn(i)&&(p=!0,i=i[sn]);let f=dt(i);n===0&&o!==null?(bh(s,o,r,e),a==null?yC(t,o,f):pi(t,o,f,a||null,!0)):n===1&&o!==null?(bh(s,o,r,e),pi(t,o,f,a||null,!0),Gy(r,f,s)):n===2?(s?.[xn]?.leave?.has(r.index)&&qy(r,f,s),ya.delete(f),Ph(s,r,e,_=>{if(ya.has(f)){ya.delete(f);return}xC(t,f,p,_)})):n===3&&(ya.delete(f),Ph(s,r,e,()=>{t.destroyNode(f)})),c!=null&&fx(t,n,e,c,r,o,a)}}function nx(n,t){NC(n,t),t[sn]=null,t[yt]=null}function ox(n,t,e,o,i,r){o[sn]=i,o[yt]=t,Pl(n,o,e,1,i,r)}function NC(n,t){t[cn].changeDetectionScheduler?.notify(9),Pl(n,t,t[we],2,null,null)}function ix(n){let t=n[tr];if(!t)return yg(n[K],n);for(;t;){let e=null;if(Kn(t))e=t[tr];else{let o=t[Ve];o&&(e=o)}if(!e){for(;t&&!t[Ht]&&t!==n;)Kn(t)&&yg(t[K],t),t=t[Xe];t===null&&(t=n),Kn(t)&&yg(t[K],t),e=t&&t[Ht]}t=e}}function Op(n,t){let e=n[li],o=e.indexOf(t);e.splice(o,1)}function bl(n,t){if(di(t))return;let e=t[we];e.destroyNode&&Pl(n,t,e,3,null,null),ix(t)}function yg(n,t){if(di(t))return;let e=Y(null);try{t[ee]&=-129,t[ee]|=256,t[Tt]&&qo(t[Tt]),ax(n,t),rx(n,t),t[K].type===1&&t[we].destroy();let o=t[Oo];if(o!==null&&Ut(t[Xe])){o!==t[Xe]&&Op(o,t);let i=t[vn];i!==null&&i.detachView(n)}Lg(t)}finally{Y(e)}}function rx(n,t){let e=n.cleanup,o=t[er];if(e!==null)for(let a=0;a=0?o[s]():o[-s].unsubscribe(),a+=2}else{let s=o[e[a+1]];e[a].call(s)}o!==null&&(t[er]=null);let i=t[Un];if(i!==null){t[Un]=null;for(let a=0;aHe&&VC(n,t,He,!1);let s=a?pe.TemplateUpdateStart:pe.TemplateCreateStart;Ce(s,i,e),e(o,i)}finally{vo(r);let s=a?pe.TemplateUpdateEnd:pe.TemplateCreateEnd;Ce(s,i,e)}}function $C(n,t,e){yx(n,t,e),(e.flags&64)===64&&xx(n,t,e)}function kp(n,t,e=At){let o=t.localNames;if(o!==null){let i=t.index+1;for(let r=0;rnull;function Ox(n){return n==="class"?"className":n==="for"?"htmlFor":n==="formaction"?"formAction":n==="innerHtml"?"innerHTML":n==="readonly"?"readOnly":n==="tabindex"?"tabIndex":n}function zC(n,t,e,o,i,r){let a=t[K];if(Ep(n,a,t,e,o)){Yn(n)&&vx(t,n.index);return}n.type&3&&(e=Ox(e)),BC(n,t,e,o,i,r)}function BC(n,t,e,o,i,r){if(n.type&3){let a=At(n,t);o=r!=null?r(o,n.value||"",e):o,i.setProperty(a,e,o)}else n.type&12}function vx(n,t){let e=Gt(t,n);e[ee]&16||(e[ee]|=64)}function yx(n,t,e){let o=e.directiveStart,i=e.directiveEnd;Yn(e)&&Cx(t,e,n.data[o+e.componentOffset]),n.firstCreatePass||Jc(e,t);let r=e.initialInputs;for(let a=o;a=s&&h<=c){let P=t.data[h],T=_[C+1];hi(P,e[h],T,r),p=!0}else if(h>c)break}}return a!==null&&Object.hasOwn(o.inputs,i)&&(hi(o,e[a],i,r),p=!0),p}function Ax(n,t){let e=Gt(t,n),o=e[K];Nx(o,e);let i=e[sn];i!==null&&e[Xi]===null&&(e[Xi]=gC(i,e[On])),Ce(pe.ComponentStart);try{Dp(o,e,e[ze])}finally{Ce(pe.ComponentEnd,e[ze])}}function Nx(n,t){for(let e=t.length;e{ui(n.lView)},consumerOnSignalRead(){this.lView[Tt]=this}});function $x(n){let t=n[Tt]??Object.create(zx);return t.lView=n,t}var zx=F(S({},po),{consumerIsAlwaysLive:!0,kind:"template",consumerMarkedDirty:n=>{let t=qn(n.lView);for(;t&&!KC(t[K]);)t=qn(t);t&&og(t)},consumerOnSignalRead(){this.lView[Tt]=this}});function KC(n){return n.type!==2}function YC(n){if(n[Gn]===null)return;let t=!0;for(;t;){let e=!1;for(let o of n[Gn])if(o.dirty&&(e=!0,o.zone===null||Zone.current===o.zone?o.run():o.zone.run(()=>o.run()),n[Gn]===null))return;t=e&&!!(n[ee]&8192)}}var Bx=100;function ZC(n,t=0){let o=n[cn].rendererFactory,i=!1;i||o.begin?.();try{Hx(n,t)}finally{i||o.end?.()}}function Hx(n,t){let e=ug();try{oa(!0),Gg(n,t);let o=0;for(;ma(n);){if(o===Bx)throw new z(103,!1);o++,Gg(n,1)}}finally{oa(e)}}function Ux(n,t,e,o){if(di(t))return;let i=t[ee],r=!1,a=!1;Ic(t);let s=!0,c=null,p=null;r||(KC(n)?(p=Fx(t),c=fo(p)):Is()===null?(s=!1,p=$x(t),c=fo(p)):t[Tt]&&(qo(t[Tt]),t[Tt]=null));try{ng(t),z_(n.bindingStartIndex),e!==null&&jC(n,t,e,2,o);let f=(i&3)===3;if(!r)if(f){let h=n.preOrderCheckHooks;h!==null&&Uc(t,h,null)}else{let h=n.preOrderHooks;h!==null&&Gc(t,h,0,null),Og(t,0)}if(a||Gx(t),YC(t),QC(t,0),n.contentQueries!==null&&pC(n,t),!r)if(f){let h=n.contentCheckHooks;h!==null&&Uc(t,h)}else{let h=n.contentHooks;h!==null&&Gc(t,h,1),Og(t,1)}Wx(n,t);let _=n.components;_!==null&&XC(t,_,0);let C=n.viewQuery;if(C!==null&&jg(2,C,o),!r)if(f){let h=n.viewCheckHooks;h!==null&&Uc(t,h)}else{let h=n.viewHooks;h!==null&&Gc(t,h,2),Og(t,2)}if(n.firstUpdatePass===!0&&(n.firstUpdatePass=!1),t[Pc]){for(let h of t[Pc])h();t[Pc]=null}r||(qC(t),t[ee]&=-73)}catch(f){throw r||ui(t),f}finally{p!==null&&(Go(p,c),s&&Vx(p)),Tc()}}function QC(n,t){for(let e=rC(n);e!==null;e=aC(e))for(let o=Ve;o0&&(n[e-1][Ht]=o[Ht]);let r=da(n,Ve+t);nx(o[K],o);let a=r[vn];a!==null&&a.detachView(r[K]),o[Xe]=null,o[Ht]=null,o[ee]&=-129}return o}function Kx(n,t,e,o){let i=Ve+o,r=e.length;o>0&&(e[i-1][Ht]=t),o-1&&(Da(t,o),da(e,o))}this._attachedToViewContainer=!1}bl(this._lView[K],this._lView)}onDestroy(t){kc(this._lView,t)}markForCheck(){Ip(this._cdRefInjectingView||this._lView,4)}detach(){this._lView[ee]&=-129}reattach(){Sc(this._lView),this._lView[ee]|=128}detectChanges(){this._lView[ee]|=1024,ZC(this._lView)}checkNoChanges(){}attachToViewContainerRef(){if(this._appRef)throw new z(902,!1);this._attachedToViewContainer=!0}detachFromAppRef(){this._appRef=null;let t=nr(this._lView),e=this._lView[Oo];e!==null&&!t&&Op(e,this._lView),NC(this._lView[K],this._lView)}attachToAppRef(t){if(this._attachedToViewContainer)throw new z(902,!1);this._appRef=t;let e=nr(this._lView),o=this._lView[Oo];o!==null&&!e&&oM(o,this._lView),Sc(this._lView)}};var ur=(()=>{class n{_declarationLView;_declarationTContainer;elementRef;static __NG_ELEMENT_ID__=Yx;constructor(e,o,i){this._declarationLView=e,this._declarationTContainer=o,this.elementRef=i}get ssrId(){return this._declarationTContainer.tView?.ssrId||null}createEmbeddedView(e,o){return this.createEmbeddedViewImpl(e,o)}createEmbeddedViewImpl(e,o,i){let r=vl(this._declarationLView,this._declarationTContainer,e,{embeddedViewInjector:o,dehydratedView:i});return new So(r)}}return n})();function Yx(){return Tp(tt(),Z())}function Tp(n,t){return n.type&4?new ur(t,n,pr(n,t)):null}function xl(n,t,e,o,i){let r=n.data[t];if(r===null)r=Zx(n,t,e,o,i),B_()&&(r.flags|=32);else if(r.type&64){r.type=e,r.value=o,r.attrs=i;let a=V_();r.injectorIndex=a===null?-1:a.injectorIndex}return or(r,!0),r}function Zx(n,t,e,o,i){let r=lg(),a=dg(),s=a?r:r&&r.parent,c=n.data[t]=Jx(n,s,e,t,o,i);return Qx(n,c,r,a),c}function Qx(n,t,e,o){n.firstChild===null&&(n.firstChild=t),e!==null&&(o?e.child==null&&t.parent!==null&&(e.child=t):e.next===null&&(e.next=t,t.prev=e))}function Jx(n,t,e,o,i,r){let a=t?t.injectorIndex:-1,s=0;return L_()&&(s|=128),{type:e,index:o,insertBeforeIndex:null,injectorIndex:a,directiveStart:-1,directiveEnd:-1,directiveStylingLast:-1,componentOffset:-1,controlDirectiveIndex:-1,customControlIndex:-1,propertyBindings:null,flags:s,providerIndexes:0,value:i,namespace:_g(),attrs:r,mergedAttrs:null,localNames:null,initialInputs:null,inputs:null,hostDirectiveInputs:null,outputs:null,hostDirectiveOutputs:null,directiveToIndex:null,tView:null,next:null,prev:null,projectionNext:null,child:null,parent:t,projection:null,styles:null,stylesWithoutHost:null,residualStyles:void 0,classes:null,classesWithoutHost:null,residualClasses:void 0,classBindings:0,styleBindings:0}}function Xx(n){let t=n[Ju]??[],o=n[Xe][we],i=[];for(let r of t)r.data[lC]!==void 0?i.push(r):e1(r,o);n[Ju]=i}function e1(n,t){let e=0,o=n.firstChild;if(o){let i=n.data[cC];for(;enull,n1=()=>null;function qg(n,t){return t1(n,t)}function iM(n,t,e){return n1(n,t,e)}var rM=class{},Ci=class{},wt=class{destroyNode=null;static __NG_ELEMENT_ID__=()=>o1()};function o1(){let n=Z(),t=tt(),e=Gt(t.index,n);return(Kn(e)?e:n)[we]}var aM=(()=>{class n{static \u0275prov=te({token:n,providedIn:"root",factory:()=>null})}return n})();function sM(n){return n.debugInfo?.className||n.type.name||null}var Wc={},nl=class{injector;parentInjector;constructor(t,e){this.injector=t,this.parentInjector=e}get(t,e,o){let i=this.injector.get(t,Wc,o);return i!==Wc||e===Wc?i:this.parentInjector.get(t,e,o)}};function Ap(n,t,e){return n[t]=e}function i1(n,t){return n[t]}function St(n,t,e){if(e===ot)return!1;let o=n[t];return Object.is(o,e)?!1:(n[t]=e,!0)}function Np(n,t,e,o){let i=St(n,t,e);return St(n,t+1,o)||i}function r1(n,t,e,o,i){let r=Np(n,t,e,o);return St(n,t+2,i)||r}function fi(n,t,e){return function o(i){let r=o.__ngNativeEl__;r!==void 0&&oy(i,r);let a=Yn(n)?Gt(n.index,t):t;Ip(a,5);let s=t[ze],c=vh(t,s,e,i),p=o.__ngNextListenerFn__;for(;p;)c=vh(t,s,p,i)&&c,p=p.__ngNextListenerFn__;return c}}function vh(n,t,e,o){let i=Y(null);try{return Ce(pe.OutputStart,t,e),e(o)!==!1}catch(r){return Ix(n,r),!1}finally{Ce(pe.OutputEnd,t,e),Y(i)}}function Rp(n,t,e,o,i,r,a,s){let c=Oc(n),p=!1,f=null;if(!o&&c&&(f=s1(t,e,r,n.index)),f!==null){let _=f.__ngLastListenerFn__||f;_.__ngNextListenerFn__=a,f.__ngLastListenerFn__=a,p=!0}else{let _=At(n,e),C=o?o(_):_;ry(e,C,r,s),o||(s.__ngNativeEl__=_);let h=i.listen(C,r,s);if(!a1(r)){let P=o?T=>o(dt(T[n.index])):n.index;cM(P,t,e,r,s,h,!1)}}return p}function a1(n){return n.startsWith("animation")||n.startsWith("transition")}function s1(n,t,e,o){let i=n.cleanup;if(i!=null)for(let r=0;rc?s[c]:null}typeof a=="string"&&(r+=2)}return null}function cM(n,t,e,o,i,r,a){let s=t.firstCreatePass?rg(t):null,c=ig(e),p=c.length;c.push(i,r),s&&s.push(o,n,p,(p+1)*(a?-1:1))}function yh(n,t,e,o,i){let r=null,a=null,s=null,c=!1,p=n.directiveToIndex.get(e.type);if(typeof p=="number"?r=p:[r,a,s]=p,a!==null&&s!==null&&n.hostDirectiveOutputs&&Object.hasOwn(n.hostDirectiveOutputs,o)){let f=n.hostDirectiveOutputs[o];for(let _=0;_=a&&C<=s)c=!0,ol(n,t,C,f[_+1],o,i);else if(C>s)break}}return Object.hasOwn(e.outputs,o)&&(c=!0,ol(n,t,r,o,o,i)),c}function ol(n,t,e,o,i,r){let a=t[e],s=t[K],p=s.data[e].outputs[o],_=a[p].subscribe(r);cM(n.index,s,t,i,r,_,!0)}function Rt(){c1()}function c1(){let n=Z(),t=Ne(),e=tt();if(t.firstCreatePass&&d1(t,e),e.controlDirectiveIndex===-1)return;An("NgSignalForms");let o=n[e.controlDirectiveIndex];t.data[e.controlDirectiveIndex].controlDef.create(o,new il(n,t,e))}function Ft(){l1()}function l1(){let n=Z(),t=Ne(),e=gi();if(e.controlDirectiveIndex===-1)return;let o=t.data[e.controlDirectiveIndex].controlDef,i=n[e.controlDirectiveIndex];o.update(i,new il(n,t,e))}var il=class{lView;tView;tNode;hasPassThrough;constructor(t,e,o){this.lView=t,this.tView=e,this.tNode=o,this.hasPassThrough=!!(o.flags&4096)}get customControl(){return this.tNode.customControlIndex!==-1?this.lView[this.tNode.customControlIndex]:void 0}get nativeElement(){return At(this.tNode,this.lView)}get descriptor(){return`<${this.tNode.value}>`}listenToCustomControlOutput(t,e){let o=this.tView.data[this.tNode.customControlIndex];yh(this.tNode,this.lView,o,t,fi(this.tNode,this.lView,e))}listenToCustomControlModel(t){let e=this.tNode.flags&1024?"valueChange":"checkedChange",o=this.tView.data[this.tNode.customControlIndex];yh(this.tNode,this.lView,o,e,fi(this.tNode,this.lView,t))}listenToDom(t,e){Rp(this.tNode,this.tView,this.lView,void 0,this.lView[we],t,e,fi(this.tNode,this.lView,e))}setInputOnDirectives(t,e){let o=this.tNode.inputs?.[t],i=this.tNode.hostDirectiveInputs?.[t];if(!o&&!i)return!1;let r=!1;if(o)for(let a of o){if(a===this.tNode.controlDirectiveIndex)continue;let s=this.tView.data[a],c=this.lView[a];hi(s,c,t,e),r=!0}if(i)for(let a=0;a0;){let i=o.shift();if(typeof i!="function"){for(let a in i.inputs)e[i.inputs[a]]=!0;let r=xh(i.directive);r!==null&&o.push(...r);continue}for(let r of i()){if(typeof r=="function")continue;if(r.inputs)for(let s=0;s1){t.flags|=4096;return}u1(n,t)}function u1(n,t){for(let e=t.directiveStart;e{let r=t.hostDirectiveInputs[o],a=t.hostDirectiveOutputs[o+"Change"];if(!r||!a)return!1;for(let s=0;s=h&&c<=P)return t.flags|=i,t.customControlIndex=C,!0}}}return!1};if(e("value",1024)||e("checked",2048))return}}function wh(n,t){return g1(n,t)&&p1(n,t+"Change")}function g1(n,t){return t in n.inputs}function p1(n,t){return t in n.outputs}var Wg=Symbol("BINDING");var Oi=new B("");function rl(n,t,e){let o=e?n.styles:null,i=e?n.classes:null,r=0;if(t!==null)for(let a=0;a0&&(e.directiveToIndex=new Map);for(let C=0;C0;){let e=n[--t];if(typeof e=="number"&&e<0)return e}return 0}function O1(n,t,e){if(e){if(t.exportAs)for(let o=0;o{let[e,o,i]=n[t],r={propName:e,templateName:t,isSignal:(o&Ol.SignalBased)!==0};return i&&(r.transform=i),r})}function S1(n){return Object.keys(n).map(t=>({propName:n[t],templateName:t}))}function k1(n,t,e){let o=t instanceof Le?t:t?.injector;return o&&n.getStandaloneInjector!==null&&(o=n.getStandaloneInjector(o)||o),o?new nl(e,o):e}function E1(n){let t=n.get(Ci,null);if(t===null)throw new z(407,!1);let e=n.get(aM,null),o=n.get(bn,null),i=n.get(Tn,null,{optional:!0});return{rendererFactory:t,sanitizer:e,changeDetectionScheduler:o,ngReflect:!1,tracingService:i}}function D1(n,t,e){let o=pM(n);return vC(t,o,o==="svg"?Qi:o==="math"?vc:e)}function I1(n){if((n&&"localName"in n&&typeof n.localName=="string"?n.localName:n?.tagName)?.toLowerCase()==="script")throw new z(905,!1)}function pM(n){return(n.selectors[0][0]||"div").toLowerCase()}var Ia=class{componentDef;ngModule;selector;componentType;ngContentSelectors;isBoundToModule;cachedInputs=null;cachedOutputs=null;get inputs(){return this.cachedInputs??=w1(this.componentDef.inputs),this.cachedInputs}get outputs(){return this.cachedOutputs??=S1(this.componentDef.outputs),this.cachedOutputs}constructor(t,e){this.componentDef=t,this.ngModule=e,this.componentType=t.type,this.selector=By(t.selectors),this.ngContentSelectors=t.ngContentSelectors??[],this.isBoundToModule=!!e}create(t,e,o,i,r,a,s){Ce(pe.DynamicComponentStart);let c=Y(null);try{let p=this.componentDef,f=k1(p,i||this.ngModule,t),_=E1(f),C=_.tracingService;return C&&C.componentCreate?C.componentCreate(sM(p),()=>this.createComponentRef(_,f,e,o,r,a,s)):this.createComponentRef(_,f,e,o,r,a,s)}finally{Y(c)}}createComponentRef(t,e,o,i,r,a,s){let c=this.componentDef,p=T1(i,c,a,r),f=t.rendererFactory.createRenderer(null,c),_=i?Mx(f,i,c.encapsulation,e):D1(c,f,s??null);I1(_);let C=e.get(Oi,null),h=A1(_,()=>e.get(Ue,null)??pp());C&&C.addHost(h);let P=a?.some(Dh)||r?.some(N=>typeof N!="function"&&N.bindings.some(Dh)),T=wp(null,p,null,512|FC(c),null,null,t,f,e,null,gC(_,e,!0));C&&gM&&h instanceof ShadowRoot&&kc(T,()=>{C.removeHost(h)}),T[He]=_,Ic(T);let A=null;try{let N=dM(He,T,2,"#host",()=>p.directiveRegistry,!0,0);wC(f,_,N),dr(_,T),$C(p,T,N),fC(p,N,T),uM(p,N),o!==void 0&&R1(N,this.ngContentSelectors,o),A=Gt(N.index,T),T[ze]=A[ze],Dp(p,T,null)}catch(N){throw A!==null&&Lg(A),Lg(T),N}finally{Ce(pe.DynamicComponentEnd),Tc()}return new al(this.componentType,T,!!P)}};function T1(n,t,e,o){let i=n?["ng-version","22.1.4"]:Hy(t.selectors[0]),r=null,a=null,s=0;if(e)for(let f of e)s+=f[Wg].requiredVars,f.create&&(f.targetIdx=0,(r??=[]).push(f)),f.update&&(f.targetIdx=0,(a??=[]).push(f));if(o)for(let f=0;f{if(e&1&&n)for(let o of n)o.create();if(e&2&&t)for(let o of t)o.update()}}function Dh(n){let t=n[Wg].kind;return t==="input"||t==="twoWay"}var al=class extends rM{_rootLView;_hasInputBindings;instance;hostView;changeDetectorRef;componentType;location;previousInputValues=null;_tNode;constructor(t,e,o){super(),this._rootLView=e,this._hasInputBindings=o,this._tNode=yc(e[K],He),this.location=pr(this._tNode,e),this.instance=Gt(this._tNode.index,e)[ze],this.hostView=this.changeDetectorRef=new So(e,void 0),this.componentType=t}setInput(t,e){this._hasInputBindings;let o=this._tNode;if(this.previousInputValues??=new Map,this.previousInputValues.has(t)&&Object.is(this.previousInputValues.get(t),e))return;let i=this._rootLView,r=Ep(o,i[K],i,t,e);this.previousInputValues.set(t,e);let a=Gt(o.index,i);Ip(a,1)}get injector(){return new xo(this._tNode,this._rootLView)}destroy(){this.hostView.destroy()}onDestroy(t){this.hostView.onDestroy(t)}};function R1(n,t,e){let o=n.projection=[];for(let i=0;i{class n{static __NG_ELEMENT_ID__=F1}return n})();function F1(){let n=tt();return fM(n,Z())}var Kg=class n extends vi{_lContainer;_hostTNode;_hostLView;constructor(t,e,o){super(),this._lContainer=t,this._hostTNode=e,this._hostLView=o}get element(){return pr(this._hostTNode,this._hostLView)}get injector(){return new xo(this._hostTNode,this._hostLView)}get parentInjector(){let t=dp(this._hostTNode,this._hostLView);if(Kh(t)){let e=Zc(t,this._hostLView),o=Yc(t),i=e[K].data[o+8];return new xo(i,e)}else return new xo(null,this._hostLView)}clear(){for(;this.length>0;)this.remove(this.length-1)}get(t){let e=Ih(this._lContainer);return e!==null&&e[t]||null}get length(){return this._lContainer.length-Ve}createEmbeddedView(t,e,o){let i,r;typeof o=="number"?i=o:o!=null&&(i=o.index,r=o.injector);let a=qg(this._lContainer,t.ssrId),s=t.createEmbeddedViewImpl(e||{},r,a);return this.insertImpl(s,i,ka(this._hostTNode,a)),s}createComponent(t,e,o,i,r,a,s){let c,p=e||{};c=p.index,o=p.injector,i=p.projectableNodes,r=p.environmentInjector||p.ngModuleRef,a=p.directives,s=p.bindings;let f=new Ia(ii(t)),_=o||this.parentInjector;if(!r&&f.ngModule==null){let N=this.parentInjector.get(Le,null);N&&(r=N)}let C=ii(f.componentType??{}),h=qg(this._lContainer,C?.id??null),P=h?.firstChild??null,T=f.create(_,i,P,r,a,s,this._getHostElementNamespace());return this.insertImpl(T.hostView,c,ka(this._hostTNode,h)),T}_getHostElementNamespace(){if(this._hostTNode.type&2){let t=this._hostTNode.parent??this._hostLView[yt];return t!==null&&t.type&2&&typeof t.value=="string"&&t.value.toLowerCase()==="foreignobject"?null:t?.namespace??null}return this._hostTNode.namespace}insert(t,e){return this.insertImpl(t,e,!0)}insertImpl(t,e,o){let i=t._lView;if(D_(i)){let s=this.indexOf(t);if(s!==-1)this.detach(s);else{let c=i[Xe],p=new n(c,c[yt],c[Xe]);p.detach(p.indexOf(t))}}let r=this._adjustIndex(e),a=this._lContainer;return yl(a,i,r,o),t.attachToViewContainerRef(),Gu(xg(a),r,t),t}move(t,e){return this.insert(t,e)}indexOf(t){let e=Ih(this._lContainer);return e!==null?e.indexOf(t):-1}remove(t){let e=this._adjustIndex(t,-1),o=Da(this._lContainer,e);o&&(da(xg(this._lContainer),e),bl(o[K],o))}detach(t){let e=this._adjustIndex(t,-1),o=Da(this._lContainer,e);return o&&da(xg(this._lContainer),e)!=null?new So(o):null}_adjustIndex(t,e=0){return t??this.length+e}};function Ih(n){return n[fa]}function xg(n){return n[fa]||(n[fa]=[])}function fM(n,t){let e,o=t[n.index];return Ut(o)?e=o:(e=eM(o,t,null,n),t[n.index]=e,Sp(t,e)),V1(e,t,n,o),new Kg(e,n,t)}function L1(n,t){let e=n[we],o=e.createComment(""),i=At(t,n),r=e.parentNode(i);return pi(e,r,o,e.nextSibling(i),!1),o}var V1=z1,j1=()=>!1;function $1(n,t,e){return j1(n,t,e)}function z1(n,t,e,o){if(n[ci])return;let i;e.type&8?i=dt(o):i=L1(t,e),n[ci]=i}var Yg=class n{queryList;matches=null;constructor(t){this.queryList=t}clone(){return new n(this.queryList)}setDirty(){this.queryList.setDirty()}},Zg=class n{queries;constructor(t=[]){this.queries=t}createEmbeddedView(t){let e=t.queries;if(e!==null){let o=t.contentQueries!==null?t.contentQueries[0]:e.length,i=[];for(let r=0;r0)o.push(a[s/2]);else{let p=r[s+1],f=t[-c];for(let _=Ve;_t.trim())}function hM(n,t,e){n.queries===null&&(n.queries=new Qg),n.queries.track(new Jg(t,e))}function Z1(n,t){let e=n.contentQueries||(n.contentQueries=[]),o=e.length?e[e.length-1]:-1;t!==o&&e.push(n.queries.length-1,t)}function Fp(n,t){return n.queries.getByIndex(t)}function Q1(n,t){let e=n[K],o=Fp(e,t);return o.crossesNgTemplate?Xg(e,n,t,[]):mM(e,n,o,t)}function Eo(n){return!!n&&typeof n.then=="function"}function Lp(n){return!!n&&typeof n.subscribe=="function"}var Mi=class{},wl=class{};var cl=class extends Mi{ngModuleType;_parent;_bootstrapComponents=[];_r3Injector;instance;destroyCbs=[];constructor(t,e,o,i=!0){super(),this.ngModuleType=t,this._parent=e;let r=p_(t);this._bootstrapComponents=Ty(r.bootstrap),this._r3Injector=hg(t,e,[{provide:Mi,useValue:this},...o],aa(t),new Set(["environment"])),i&&this.resolveInjectorInitializers()}resolveInjectorInitializers(){this._r3Injector.resolveInjectorInitializers(),this.instance=this._r3Injector.get(this.ngModuleType)}get injector(){return this._r3Injector}destroy(){let t=this._r3Injector;!t.destroyed&&t.destroy(),this.destroyCbs.forEach(e=>e()),this.destroyCbs=null}onDestroy(t){this.destroyCbs.push(t)}},ll=class extends wl{moduleType;constructor(t){super(),this.moduleType=t}create(t){return new cl(this.moduleType,t,[])}};var Ta=class extends Mi{injector;instance=null;constructor(t){super();let e=new ni([...t.providers,{provide:Mi,useValue:this}],t.parent||pa(),t.debugName,new Set(["environment"]));this.injector=e,t.runEnvironmentInitializers&&e.resolveInjectorInitializers()}destroy(){this.injector.destroy()}onDestroy(t){this.injector.onDestroy(t)}};function Va(n,t,e=null){return new Ta({providers:n,parent:t,debugName:e,runEnvironmentInitializers:!0}).injector}var J1=(()=>{class n{_injector;cachedInjectors=new Map;constructor(e){this._injector=e}getOrCreateStandaloneInjector(e){if(!e.standalone)return null;if(!this.cachedInjectors.has(e)){let o=Wu(!1,e.type),i=o.length>0?Va([o],this._injector,""):null;this.cachedInjectors.set(e,i)}return this.cachedInjectors.get(e)}ngOnDestroy(){try{for(let e of this.cachedInjectors.values())e!==null&&e.destroy()}finally{this.cachedInjectors.clear()}}static \u0275prov=te({token:n,providedIn:"environment",factory:()=>new n(Q(Le))})}return n})();function Ge(n){return Na(()=>{let t=CM(n),e=F(S({},t),{decls:n.decls,vars:n.vars,template:n.template,consts:n.consts||null,ngContentSelectors:n.ngContentSelectors,onPush:n.changeDetection!==up.Eager,directiveDefs:null,pipeDefs:null,dependencies:t.standalone&&n.dependencies||null,getStandaloneInjector:t.standalone?i=>i.get(J1).getOrCreateStandaloneInjector(e):null,getExternalStyles:null,signals:n.signals??!1,data:n.data||{},encapsulation:n.encapsulation||un.Emulated,styles:n.styles||ht,_:null,schemas:n.schemas||null,tView:null,id:""});t.standalone&&An("NgStandalone"),MM(e);let o=n.dependencies;return e.directiveDefs=Th(o,X1),e.pipeDefs=Th(o,f_),e.id=nw(e),e})}function X1(n){return ii(n)||Bu(n)}function Jn(n){return Na(()=>({type:n.type,bootstrap:n.bootstrap||ht,declarations:n.declarations||ht,imports:n.imports||ht,exports:n.exports||ht,transitiveCompileScopes:null,schemas:n.schemas||null,id:n.id||null}))}function ew(n,t){if(n==null)return Mo;let e={};for(let o in n)if(Object.hasOwn(n,o)){let i=n[o],r,a,s,c;Array.isArray(i)?(s=i[0],r=i[1],a=i[2]??r,c=i[3]||null):(r=i,a=i,s=Ol.None,c=null),e[r]=[o,s,c],t[r]=a}return e}function tw(n){if(n==null)return Mo;let t={};for(let e in n)Object.hasOwn(n,e)&&(t[n[e]]=e);return t}function qe(n){return Na(()=>{let t=CM(n);return MM(t),t})}function Sl(n){return{type:n.type,name:n.name,factory:null,pure:n.pure!==!1,standalone:n.standalone??!0,onDestroy:n.type.prototype.ngOnDestroy||null}}function CM(n){let t={};return{type:n.type,providersResolver:null,viewProvidersResolver:null,factory:null,hostBindings:n.hostBindings||null,hostVars:n.hostVars||0,hostAttrs:n.hostAttrs||null,contentQueries:n.contentQueries||null,declaredInputs:t,inputConfig:n.inputs||Mo,exportAs:n.exportAs||null,standalone:n.standalone??!0,signals:n.signals===!0,selectors:n.selectors||ht,viewQuery:n.viewQuery||null,features:n.features||null,setInput:null,resolveHostDirectives:null,hostDirectives:null,controlDef:null,signalFormsInputPresence:null,inputs:ew(n.inputs,t),outputs:tw(n.outputs),debugInfo:null}}function MM(n){n.features?.forEach(t=>t(n))}function Th(n,t){return n?()=>{let e=typeof n=="function"?n():n,o=[];for(let i of e){let r=t(i);r!==null&&o.push(r)}return o}:null}function nw(n){let t=0,e=typeof n.consts=="function"?"":n.consts,o=[n.selectors,n.ngContentSelectors,n.hostVars,n.hostAttrs,e,n.vars,n.decls,n.encapsulation,n.standalone,n.signals,n.exportAs,JSON.stringify(n.inputs),JSON.stringify(n.outputs),Object.getOwnPropertyNames(n.type.prototype),!!n.contentQueries,!!n.viewQuery];for(let r of o.join("|"))t=Math.imul(31,t)+r.charCodeAt(0)<<0;return t+=2147483648,"c"+t}var bM=new B("");var Vp=(()=>{class n{resolve;reject;initialized=!1;done=!1;donePromise=new Promise((e,o)=>{this.resolve=e,this.reject=o});appInits=D(bM,{optional:!0})??[];injector=D(Ct);constructor(){}runInitializers(){if(this.initialized)return;let e=[];for(let i of this.appInits){let r=et(this.injector,i);if(Eo(r))e.push(r);else if(Lp(r)){let a=new Promise((s,c)=>{r.subscribe({complete:s,error:c})});e.push(a)}}let o=()=>{this.done=!0,this.resolve()};Promise.all(e).then(()=>{o()}).catch(i=>{this.reject(i)}),e.length===0&&o(),this.initialized=!0}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function jp(n){return t=>{t.controlDef={create:(e,o)=>{e?.\u0275ngControlCreate(o)},update:(e,o)=>{e?.\u0275ngControlUpdate?.(o)},passThroughInput:n}}}function ow(n){return Object.getPrototypeOf(n.prototype).constructor}function gn(n){let t=ow(n.type),e=!0,o=[n];for(;t&&t!==Function.prototype&&t!==Object.prototype;){let i,r=Object.hasOwn(t,ca)?t[ca]:void 0,a=Object.hasOwn(t,la)?t[la]:void 0;if(wn(n))i=r??a;else{if(r)throw new z(903,!1);i=a}if(i){if(e){o.push(i);let c=n;c.inputs=wg(n.inputs),c.declaredInputs=wg(n.declaredInputs),c.outputs=wg(n.outputs);let p=i.hostBindings;p&&cw(n,p);let f=i.viewQuery,_=i.contentQueries;if(f&&aw(n,f),_&&sw(n,_),iw(n,i),g_(n.outputs,i.outputs),wn(i)&&i.data.animation){let C=n.data;C.animation=(C.animation||[]).concat(i.data.animation)}}let s=i.features;if(s)for(let c=0;c=0;o--){let i=n[o];i.hostVars=t+=i.hostVars,i.hostAttrs=lr(i.hostAttrs,e=lr(e,i.hostAttrs))}}function wg(n){return n===Mo?{}:n===ht?[]:n}function aw(n,t){let e=n.viewQuery;e?n.viewQuery=(o,i)=>{t(o,i),e(o,i)}:n.viewQuery=t}function sw(n,t){let e=n.contentQueries;e?n.contentQueries=(o,i,r)=>{t(o,i,r),e(o,i,r)}:n.contentQueries=t}function cw(n,t){let e=n.hostBindings;e?n.hostBindings=(o,i)=>{t(o,i),e(o,i)}:n.hostBindings=t}function lw(n,t,e,o,i,r,a,s){if(e.firstCreatePass){n.mergedAttrs=lr(n.mergedAttrs,n.attrs);let f=n.tView=xp(2,n,i,r,a,e.directiveRegistry,e.pipeRegistry,null,e.schemas,e.consts,null);e.queries!==null&&(e.queries.template(e,n),f.queries=e.queries.embeddedTView(n))}s&&(n.flags|=s),or(n,!1);let c=dw(e,t,n,o);Ac()&&vp(e,t,c,n),dr(c,t);let p=eM(c,t,c,n);t[o+He]=p,Sp(t,p),$1(p,n,t)}function dl(n,t,e,o,i,r,a,s,c,p,f){let _=e+He,C;if(t.firstCreatePass){if(C=xl(t,_,4,a||null,s||null),p!=null){let h=Sn(t.consts,p);C.localNames=[];for(let P=0;P{class n{log(e){console.log(e)}warn(e){console.warn(e)}static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"platform"})}return n})();var zp=new B("");var kl=new B("");function PM(){ou(()=>{let n="";throw new z(600,n)})}var gw=10;var Xn=(()=>{class n{_runningTick=!1;_destroyed=!1;_destroyListeners=[];_views=[];internalErrorHandler=D(Nt);afterRenderManager=D(bp);zonelessEnabled=D(ar);rootEffectScheduler=D(Vc);dirtyFlags=0;tracingSnapshot=null;allTestViews=new Set;autoDetectTestViews=new Set;includeAllTestViews=!1;afterTick=new ye;get allViews(){return[...(this.includeAllTestViews?this.allTestViews:this.autoDetectTestViews).keys(),...this._views]}get destroyed(){return this._destroyed}componentTypes=[];components=[];internalPendingTask=D(kn);get isStable(){return this.internalPendingTask.hasPendingTasksObservable.pipe(Pe(e=>!e))}constructor(){D(Tn,{optional:!0})}whenStable(){let e;return new Promise(o=>{e=this.isStable.subscribe({next:i=>{i&&o()}})}).finally(()=>{e.unsubscribe()})}_injector=D(Le);_rendererFactory=null;get injector(){return this._injector}bootstrap(e,o){return this.bootstrapImpl(e,o)}bootstrapImpl(e,o,i=Ct.NULL){return this._injector.get(ge).run(()=>{if(Ce(pe.BootstrapComponentStart),!this._injector.get(Vp).done){let N="";throw new z(405,N)}let s=ii(e),c=this._injector.get(Mi),p=new Ia(s,c);this.componentTypes.push(e);let{hostElement:f,directives:_,bindings:C}=pw(o),h=f||p.selector,P=p.create(i,[],h,c.injector,_,C),T=P.location.nativeElement,A=P.injector.get(zp,null);return A?.registerApplication(T),P.onDestroy(()=>{this.detachView(P.hostView),wa(this.components,P),A?.unregisterApplication(T)}),this._loadComponent(P),Ce(pe.BootstrapComponentEnd,P),P})}tick(){this.zonelessEnabled||(this.dirtyFlags|=1),this._tick()}_tick(){Ce(pe.ChangeDetectionStart),this.tracingSnapshot!==null?this.tracingSnapshot.run(Cl.CHANGE_DETECTION,this.tickImpl):this.tickImpl()}tickImpl=()=>{if(this._runningTick)throw Ce(pe.ChangeDetectionEnd),new z(101,!1);let e=Y(null);try{this._runningTick=!0,this.synchronize()}finally{this._runningTick=!1,this.tracingSnapshot?.dispose(),this.tracingSnapshot=null,Y(e),this.afterTick.next(),Ce(pe.ChangeDetectionEnd)}};synchronize(){this._rendererFactory===null&&!this._injector.destroyed&&(this._rendererFactory=this._injector.get(Ci,null,{optional:!0}));let e=0;for(;this.dirtyFlags!==0&&e++ma(e))){this.dirtyFlags|=2;return}else this.dirtyFlags&=-8}attachView(e){let o=e;this._views.push(o),o.attachToAppRef(this)}detachView(e){let o=e;wa(this._views,o),o.detachFromAppRef()}_loadComponent(e){this.attachView(e.hostView);try{this.tick()}catch(i){this.internalErrorHandler(i)}this.components.push(e),this._injector.get(kl,[]).forEach(i=>i(e))}ngOnDestroy(){if(!this._destroyed)try{this._destroyListeners.forEach(e=>e()),this._views.slice().forEach(e=>e.destroy())}finally{this._destroyed=!0,this._views=[],this._destroyListeners=[]}}onDestroy(e){return this._destroyListeners.push(e),()=>wa(this._destroyListeners,e)}destroy(){if(this._destroyed)throw new z(406,!1);let e=this._injector;e.destroy&&!e.destroyed&&e.destroy()}get viewCount(){return this._views.length}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function pw(n){return n===void 0||typeof n=="string"||n instanceof Element?{hostElement:n}:n}function wa(n,t){let e=n.indexOf(t);e>-1&&n.splice(e,1)}function Wt(n,t,e,o){let i=Z(),r=Zn();if(St(i,r,t)){let a=Ne(),s=gi();kx(s,i,n,t,e,o)}return Wt}function De(){return Z()[xt][ze]}var ep=class{destroy(t){}updateValue(t,e){}swap(t,e){let o=Math.min(t,e),i=Math.max(t,e),r=this.detach(i);if(i-o>1){let a=this.detach(o);this.attach(o,r),this.attach(i,a)}else this.attach(o,r)}move(t,e){this.attach(e,this.detach(t))}};function Sg(n,t,e,o,i){return n===e&&Object.is(t,o)?1:Object.is(i(n,t),i(e,o))?-1:0}function fw(n,t,e,o){let i,r,a=0,s=n.length-1,c=void 0;if(Array.isArray(t)){Y(o);let p=t.length-1;for(Y(null);a<=s&&a<=p;){let f=n.at(a),_=t[a],C=Sg(a,f,a,_,e);if(C!==0){C<0&&n.updateValue(a,_),a++;continue}let h=n.at(s),P=t[p],T=Sg(s,h,p,P,e);if(T!==0){T<0&&n.updateValue(s,P),s--,p--;continue}let A=e(a,f),N=e(s,h),E=e(a,_);if(Object.is(E,N)){let U=e(p,P);Object.is(U,A)?(n.swap(a,s),n.updateValue(s,P),p--,s--):n.move(s,a),n.updateValue(a,_),a++;continue}if(i??=new ul,r??=Nh(n,a,s,e),tp(n,i,a,E))n.updateValue(a,_),a++,s++;else if(r.has(E))i.set(A,n.detach(a)),s--;else{let U=n.create(a,t[a]);n.attach(a,U),a++,s++}}for(;a<=p;)Ah(n,i,e,a,t[a]),a++}else if(t!=null){Y(o);let p=t[Symbol.iterator]();Y(null);let f=p.next();for(;!f.done&&a<=s;){let _=n.at(a),C=f.value,h=Sg(a,_,a,C,e);if(h!==0)h<0&&n.updateValue(a,C),a++,f=p.next();else{i??=new ul,r??=Nh(n,a,s,e);let P=e(a,C);if(tp(n,i,a,P))n.updateValue(a,C),a++,s++,f=p.next();else if(!r.has(P))n.attach(a,n.create(a,C)),a++,s++,f=p.next();else{let T=e(a,_);i.set(T,n.detach(a)),s--}}}for(;!f.done;)Ah(n,i,e,n.length,f.value),f=p.next()}for(;a<=s;)n.destroy(n.detach(s--));i?.forEach(p=>{n.destroy(p)})}function tp(n,t,e,o){return t!==void 0&&t.has(o)?(n.attach(e,t.get(o)),t.delete(o),!0):!1}function Ah(n,t,e,o,i){if(tp(n,t,o,e(o,i)))n.updateValue(o,i);else{let r=n.create(o,i);n.attach(o,r)}}function Nh(n,t,e,o){let i=new Set;for(let r=t;r<=e;r++)i.add(o(r,n.at(r)));return i}var ul=class{kvMap=new Map;_vMap=void 0;has(t){return this.kvMap.has(t)}delete(t){if(!this.has(t))return!1;let e=this.kvMap.get(t);return this._vMap!==void 0&&this._vMap.has(e)?(this.kvMap.set(t,this._vMap.get(e)),this._vMap.delete(e)):this.kvMap.delete(t),!0}get(t){return this.kvMap.get(t)}set(t,e){if(this.kvMap.has(t)){let o=this.kvMap.get(t);this._vMap===void 0&&(this._vMap=new Map);let i=this._vMap;for(;i.has(o);)o=i.get(o);i.set(o,e)}else this.kvMap.set(t,e)}forEach(t){for(let[e,o]of this.kvMap)if(t(o,e),this._vMap!==void 0){let i=this._vMap;for(;i.has(o);)o=i.get(o),t(o,e)}}};function M(n,t,e,o,i,r,a,s){An("NgControlFlow");let c=Z(),p=Ne(),f=Sn(p.consts,r);return dl(c,p,n,t,e,o,i,f,256,a,s),Bp}function Bp(n,t,e,o,i,r,a,s){An("NgControlFlow");let c=Z(),p=Ne(),f=Sn(p.consts,r);return dl(c,p,n,t,e,o,i,f,512,a,s),Bp}function b(n,t){An("NgControlFlow");let e=Z(),o=Zn(),i=e[o]!==ot?e[o]:-1,r=i!==-1?gl(e,He+i):void 0,a=0;if(St(e,o,n)){let s=Y(null);try{if(r!==void 0&&nM(r,a),n!==-1){let c=He+n,p=gl(e,c),f=rp(e[K],c),_=iM(p,f,e),C=vl(e,f,t,{dehydratedView:_});yl(p,C,a,ka(f,_))}}finally{Y(s)}}else if(r!==void 0){let s=tM(r,a);s!==void 0&&(s[ze]=t)}}var np=class{lContainer;$implicit;$index;constructor(t,e,o){this.lContainer=t,this.$implicit=e,this.$index=o}get $count(){return this.lContainer.length-Ve}};function Do(n){return n}function Ie(n,t){return t}var op=class{hasEmptyBlock;trackByFn;liveCollection;constructor(t,e,o){this.hasEmptyBlock=t,this.trackByFn=e,this.liveCollection=o}};function G(n,t,e,o,i,r,a,s,c,p,f,_,C){An("NgControlFlow");let h=Z(),P=Ne(),T=c!==void 0,A=Z(),N=s?a.bind(A[xt][ze]):a,E=new op(T,N);A[He+n]=E,dl(h,P,n+1,t,e,o,i,Sn(P.consts,r),256),T&&dl(h,P,n+2,c,p,f,_,Sn(P.consts,C),512)}var ip=class extends ep{lContainer;hostLView;templateTNode;operationsCounter=void 0;needsIndexUpdate=!1;constructor(t,e,o){super(),this.lContainer=t,this.hostLView=e,this.templateTNode=o}get length(){return this.lContainer.length-Ve}at(t){return this.getLView(t)[ze].$implicit}attach(t,e){let o=e[Xi];this.needsIndexUpdate||=t!==this.length,yl(this.lContainer,e,t,ka(this.templateTNode,o)),mw(this.lContainer,t)}detach(t){return this.needsIndexUpdate||=t!==this.length-1,_w(this.lContainer,t),hw(this.lContainer,t)}create(t,e){let o=qg(this.lContainer,this.templateTNode.tView.ssrId);return vl(this.hostLView,this.templateTNode,new np(this.lContainer,e,t),{dehydratedView:o})}destroy(t){bl(t[K],t)}updateValue(t,e){this.getLView(t)[ze].$implicit=e}reset(){this.needsIndexUpdate=!1}updateIndexes(){if(this.needsIndexUpdate)for(let t=0;t0){let r=o[On];Zy(r,i),wo.delete(o[yn]),i.detachedLeaveAnimationFns=void 0}}function _w(n,t){if(n.length<=Ve)return;let e=Ve+t,o=n[e],i=o?o[xn]:void 0;i&&i.leave&&i.leave.size>0&&(i.detachedLeaveAnimationFns=[])}function hw(n,t){return Da(n,t)}function Cw(n,t){return tM(n,t)}function rp(n,t){return yc(n,t)}function R(n,t,e){let o=Z(),i=Zn();if(St(o,i,t)){let r=Ne(),a=gi();zC(a,o,n,t,o[we],e)}return R}function ap(n,t,e,o,i){Ep(t,n,e,i?"class":"style",o)}function l(n,t,e,o){let i=Z(),r=i[K],a=n+He,s=r.firstCreatePass?dM(a,i,2,t,Sx,F_(),e,o):r.data[a];if(Yn(s)){let c=i[cn].tracingService;if(c&&c.componentCreate){let p=r.data[s.directiveStart+s.componentOffset];return c.componentCreate(sM(p),()=>(Rh(n,t,i,s,o),l))}}return Rh(n,t,i,s,o),l}function Rh(n,t,e,o,i){if(HC(o,e,n,t,OM),Oc(o)){let r=e[K];$C(r,e,o),fC(r,o,e)}i!=null&&kp(e,o)}function d(){let n=Ne(),t=tt(),e=UC(t);return n.firstCreatePass&&uM(n,e),sg(e)&&cg(),ag(),e.classesWithoutHost!=null&&Vv(e)&&ap(n,e,Z(),e.classesWithoutHost,!0),e.stylesWithoutHost!=null&&jv(e)&&ap(n,e,Z(),e.stylesWithoutHost,!1),d}function $(n,t,e,o){return l(n,t,e,o),d(),$}function j(n,t,e,o){let i=Z(),r=i[K],a=n+He,s=r.firstCreatePass?y1(a,r,2,t,e,o):r.data[a];return HC(s,i,n,t,OM),o!=null&&kp(i,s),j}function H(){let n=tt(),t=UC(n);return sg(t)&&cg(),ag(),H}function yi(n,t,e,o){return j(n,t,e,o),H(),yi}var OM=(n,t,e,o,i)=>(Nc(!0),vC(t[we],o,_g()));function L(){return Z()}function je(n,t,e){let o=Z(),i=Zn();if(St(o,i,t)){let r=Ne(),a=gi();BC(a,o,n,t,o[we],e)}return je}var va=void 0;function Mw(n){let t=Math.floor(Math.abs(n)),e=n.toString().replace(/^[^.]*\.?/,"").length;return t===1&&e===0?1:5}var bw=["en",[["a","p"],["AM","PM"]],[["AM","PM"]],[["S","M","T","W","T","F","S"],["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],["Su","Mo","Tu","We","Th","Fr","Sa"]],va,[["J","F","M","A","M","J","J","A","S","O","N","D"],["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],["January","February","March","April","May","June","July","August","September","October","November","December"]],va,[["B","A"],["BC","AD"],["Before Christ","Anno Domini"]],0,[6,0],["M/d/yy","MMM d, y","MMMM d, y","EEEE, MMMM d, y"],["h:mm\u202Fa","h:mm:ss\u202Fa","h:mm:ss\u202Fa z","h:mm:ss\u202Fa zzzz"],["{1}, {0}",va,va,va],[".",",",";","%","+","-","E","\xD7","\u2030","\u221E","NaN",":"],["#,##0.###","#,##0%","\xA4#,##0.00","#E0"],"USD","$","US Dollar",{},"ltr",Mw],kg=Object.create(null);function Lt(n){let t=Pw(n),e=Fh(t);if(e)return e;let o=t.split("-")[0];if(e=Fh(o),e)return e;if(o==="en")return bw;throw new z(701,!1)}function Fh(n){if(!(n in kg)){let t=an.ng&&an.ng.common&&an.ng.common.locales&&an.ng.common.locales[n];return t!==void 0&&(kg[n]=t),t}return kg[n]}var Ze={LocaleId:0,DayPeriodsFormat:1,DayPeriodsStandalone:2,DaysFormat:3,DaysStandalone:4,MonthsFormat:5,MonthsStandalone:6,Eras:7,FirstDayOfWeek:8,WeekendRange:9,DateFormat:10,TimeFormat:11,DateTimeFormat:12,NumberSymbols:13,NumberFormats:14,CurrencyCode:15,CurrencySymbol:16,CurrencyName:17,Currencies:18,Directionality:19,PluralCase:20,ExtraData:21};function Pw(n){return n.toLowerCase().replace(/_/g,"-")}var ja="en-US";var Ow=ja;function vM(n){typeof n=="string"&&(Ow=n.toLowerCase().replace(/_/g,"-"))}function k(n,t,e){let o=Z(),i=Ne(),r=tt();return yM(i,o,o[we],r,n,t,e),k}function ue(n,t,e){let o=Z(),i=Ne(),r=tt();return(r.type&3||e)&&Rp(r,i,o,e,o[we],n,t,fi(r,o,t)),ue}function yM(n,t,e,o,i,r,a){let s=!0,c=null;if((o.type&3||a)&&(c??=fi(o,t,r),Rp(o,n,t,a,e,i,r,c)&&(s=!1)),s){let p=o.outputs?.[i],f=o.hostDirectiveOutputs?.[i];if(f&&f.length)for(let _=0;_>17&32767}function vw(n){return(n&2)==2}function yw(n,t){return n&131071|t<<17}function sp(n){return n|2}function gr(n){return(n&131068)>>2}function Eg(n,t){return n&-131069|t<<2}function xw(n){return(n&1)===1}function cp(n){return n|1}function ww(n,t,e,o,i,r){let a=r?t.classBindings:t.styleBindings,s=bi(a),c=gr(a);n[o]=e;let p=!1,f;if(Array.isArray(e)){let _=e;f=_[1],(f===null||Ji(_,f)>0)&&(p=!0)}else f=e;if(i)if(c!==0){let C=bi(n[s+1]);n[o+1]=Hc(C,s),C!==0&&(n[C+1]=Eg(n[C+1],o)),n[s+1]=yw(n[s+1],o)}else n[o+1]=Hc(s,0),s!==0&&(n[s+1]=Eg(n[s+1],o)),s=o;else n[o+1]=Hc(c,0),s===0?s=o:n[c+1]=Eg(n[c+1],o),c=o;p&&(n[o+1]=sp(n[o+1])),Lh(n,f,o,!0),Lh(n,f,o,!1),Sw(t,f,n,o,r),a=Hc(s,c),r?t.classBindings=a:t.styleBindings=a}function Sw(n,t,e,o,i){let r=i?n.residualClasses:n.residualStyles;r!=null&&typeof t=="string"&&Ji(r,t)>=0&&(e[o+1]=cp(e[o+1]))}function Lh(n,t,e,o){let i=n[e+1],r=t===null,a=o?bi(i):gr(i),s=!1;for(;a!==0&&(s===!1||r);){let c=n[a],p=n[a+1];kw(c,t)&&(s=!0,n[a+1]=o?cp(p):sp(p)),a=o?bi(p):gr(p)}s&&(n[e+1]=o?sp(i):cp(i))}function kw(n,t){return n===null||t==null||(Array.isArray(n)?n[1]:n)===t?!0:Array.isArray(n)&&typeof t=="string"?Ji(n,t)>=0:!1}var dn={textEnd:0,key:0,keyEnd:0,value:0,valueEnd:0};function Ew(n){return n.substring(dn.key,dn.keyEnd)}function Dw(n){return Iw(n),xM(n,wM(n,0,dn.textEnd))}function xM(n,t){let e=dn.textEnd;return e===t?-1:(t=dn.keyEnd=Tw(n,dn.key=t,e),wM(n,t,e))}function Iw(n){dn.key=0,dn.keyEnd=0,dn.value=0,dn.valueEnd=0,dn.textEnd=n.length}function wM(n,t,e){for(;t32;)t++;return t}function Se(n,t,e){return SM(n,t,e,!1),Se}function w(n,t){return SM(n,t,null,!0),w}function Dl(n){Nw($w,Aw,n,!0)}function Aw(n,t){for(let e=Dw(t);e>=0;e=xM(t,e))Mc(n,Ew(t),!0)}function SM(n,t,e,o){let i=Z(),r=Ne(),a=_a(2);if(r.firstUpdatePass&&EM(r,n,a,o),t!==ot&&St(i,a,t)){let s=r.data[qt()];DM(r,s,i,i[we],n,i[a+1]=Bw(t,e),o,a)}}function Nw(n,t,e,o){let i=Ne(),r=_a(2);i.firstUpdatePass&&EM(i,null,r,o);let a=Z();if(e!==ot&&St(a,r,e)){let s=i.data[qt()];if(IM(s,o)&&!kM(i,r)){let c=o?s.classesWithoutHost:s.stylesWithoutHost;c!==null&&(e=fc(c,e||"")),ap(i,s,a,e,o)}else zw(i,s,a,a[we],a[r+1],a[r+1]=jw(n,t,e),o,r)}}function kM(n,t){return t>=n.expandoStartIndex}function EM(n,t,e,o){let i=n.data;if(i[e+1]===null){let r=i[qt()],a=kM(n,e);IM(r,o)&&t===null&&!a&&(t=!1),t=Rw(i,r,t,o),ww(i,r,t,e,a,o)}}function Rw(n,t,e,o){let i=G_(n),r=o?t.residualClasses:t.residualStyles;if(i===null)(o?t.classBindings:t.styleBindings)===0&&(e=Dg(null,n,t,e,o),e=Aa(e,t.attrs,o),r=null);else{let a=t.directiveStylingLast;if(a===-1||n[a]!==i)if(e=Dg(i,n,t,e,o),r===null){let c=Fw(n,t,o);c!==void 0&&Array.isArray(c)&&(c=Dg(null,n,t,c[1],o),c=Aa(c,t.attrs,o),Lw(n,t,o,c))}else r=Vw(n,t,o)}return r!==void 0&&(o?t.residualClasses=r:t.residualStyles=r),e}function Fw(n,t,e){let o=e?t.classBindings:t.styleBindings;if(gr(o)!==0)return n[bi(o)]}function Lw(n,t,e,o){let i=e?t.classBindings:t.styleBindings;n[bi(i)]=o}function Vw(n,t,e){let o,i=t.directiveEnd;for(let r=1+t.directiveStylingLast;r0;){let c=n[i],p=Array.isArray(c),f=p?c[1]:c,_=f===null,C=e[i+1];C===ot&&(C=_?ht:void 0);let h=_?bc(C,o):f===o?C:void 0;if(p&&!pl(h)&&(h=bc(c,o)),pl(h)&&(s=h,a))return s;let P=n[i+1];i=a?bi(P):gr(P)}if(t!==null){let c=r?t.residualClasses:t.residualStyles;c!=null&&(s=bc(c,o))}return s}function pl(n){return n!==void 0}function Bw(n,t){return n==null||n===""||(typeof t=="string"?n=_i(n)+t:typeof n=="object"&&(n=aa(_i(n)))),n}function IM(n,t){return(n.flags&(t?8:16))!==0}function u(n,t=""){let e=Z(),o=Ne(),i=n+He,r=o.firstCreatePass?xl(o,i,1,t,null):o.data[i],a=Hw(o,e,r,t);e[i]=a,Ac()&&vp(o,e,a,r),or(r,!1)}var Hw=(n,t,e,o)=>(Nc(!0),xy(t[we],o));function Uw(n,t,e,o=""){return St(n,Zn(),e)?t+rn(e)+o:ot}function Gw(n,t,e,o,i,r=""){let a=gg(),s=Np(n,a,e,i);return _a(2),s?t+rn(e)+o+rn(i)+r:ot}function qw(n,t,e,o,i,r,a,s=""){let c=gg(),p=r1(n,c,e,i,a);return _a(3),p?t+rn(e)+o+rn(i)+r+rn(a)+s:ot}function O(n){return V("",n),O}function V(n,t,e){let o=Z(),i=Uw(o,n,t,e);return i!==ot&&Hp(o,qt(),i),V}function We(n,t,e,o,i){let r=Z(),a=Gw(r,n,t,e,o,i);return a!==ot&&Hp(r,qt(),a),We}function mr(n,t,e,o,i,r,a){let s=Z(),c=qw(s,n,t,e,o,i,r,a);return c!==ot&&Hp(s,qt(),c),mr}function Hp(n,t,e){let o=eg(t,n);wy(n[we],o,e)}function _r(n,t,e){jc(t)&&(t=t());let o=Z(),i=Zn();if(St(o,i,t)){let r=Ne(),a=gi();zC(a,o,n,t,o[we],e)}return _r}function $a(n,t){let e=jc(n);return e&&n.set(t),e}function hr(n,t){let e=Z(),o=Ne(),i=tt();return yM(o,e,e[we],i,n,t),hr}function xi(n){return St(Z(),Zn(),n)?rn(n):ot}function jh(n,t,e){let o=Ne();o.firstCreatePass&&TM(t,o.data,o.blueprint,wn(n),e)}function TM(n,t,e,o,i){if(n=lt(n),Array.isArray(n))for(let r=0;r>20;if(ti(n)||!n.multi){let h=new mi(p,i,le,null),P=Tg(c,t,i?f:f+C,_);P===-1?(Rg(Jc(s,a),r,c),Ig(r,n,t.length),t.push(c),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=1048576),e.push(h),a.push(h)):(e[P]=h,a[P]=h)}else{let h=Tg(c,t,f+C,_),P=Tg(c,t,f,f+C),T=h>=0&&e[h],A=P>=0&&e[P];if(i&&!A||!i&&!T){Rg(Jc(s,a),r,c);let N=Yw(i?Kw:Ww,e.length,i,o,p,n);!i&&A&&(e[P].providerFactory=N),Ig(r,n,t.length,0),t.push(c),s.directiveStart++,s.directiveEnd++,i&&(s.providerIndexes+=1048576),e.push(N),a.push(N)}else{let N=AM(e[i?P:h],p,!i&&o);Ig(r,n,h>-1?h:P,N)}!i&&o&&A&&e[P].componentProviders++}}}function Ig(n,t,e,o){let i=ti(t),r=y_(t);if(i||r){let c=(r?lt(t.useClass):t).prototype.ngOnDestroy;if(c){let p=n.destroyHooks||(n.destroyHooks=[]);if(!i&&t.multi){let f=p.indexOf(e);f===-1?p.push(e,[o,c]):p[f+1].push(o,c)}else p.push(e,c)}}}function AM(n,t,e){return e&&n.componentProviders++,n.multi.push(t)-1}function Tg(n,t,e,o){for(let i=e;i{e.providersResolver=(o,i)=>jh(o,i?i(n):n,!1),t&&(e.viewProvidersResolver=(o,i)=>jh(o,i?i(t):t,!0))}}function za(n,t){let e=ir()+n,o=Z();return o[e]===ot?Ap(o,e,t()):i1(o,e)}function Up(n,t,e){return RM(Z(),ir(),n,t,e)}function Gp(n,t,e,o){return FM(Z(),ir(),n,t,e,o)}function NM(n,t){let e=n[t];return e===ot?void 0:e}function RM(n,t,e,o,i,r){let a=t+e;return St(n,a,i)?Ap(n,a+1,r?o.call(r,i):o(i)):NM(n,a+1)}function FM(n,t,e,o,i,r,a){let s=t+e;return Np(n,s,i,r)?Ap(n,s+2,a?o.call(a,i,r):o(i,r)):NM(n,s+2)}function it(n,t){let e=Ne(),o,i=n+He;e.firstCreatePass?(o=Zw(t,e.pipeRegistry),e.data[i]=o,o.onDestroy&&(e.destroyHooks??=[]).push(i,o.onDestroy)):o=e.data[i];let r=o.factory||(o.factory=Co(o.type,!0)),a,s=_t(le);try{let c=Qc(!1),p=r();return Qc(c),tg(e,Z(),i,p),p}finally{_t(s)}}function Zw(n,t){if(t)for(let e=t.length-1;e>=0;e--){let o=t[e];if(n===o.name)return o}}function wi(n,t,e){let o=n+He,i=Z(),r=xc(i,o);return LM(i,o)?RM(i,ir(),t,r.transform,e,r):r.transform(e)}function Et(n,t,e,o){let i=n+He,r=Z(),a=xc(r,i);return LM(r,i)?FM(r,ir(),t,a.transform,e,o,a):a.transform(e,o)}function LM(n,t){return n[K].data[t].pure}var VM=(()=>{class n{applicationErrorHandler=D(Nt);appRef=D(Xn);taskService=D(kn);ngZone=D(ge);zonelessEnabled=D(ar);tracing=D(Tn,{optional:!0});zoneIsDefined=typeof Zone<"u"&&!!Zone.root.run;schedulerTickApplyArgs=[{data:{__scheduler_tick__:!0}}];subscriptions=new Be;angularZoneId=this.zoneIsDefined?this.ngZone._inner?.get(ia):null;scheduleInRootZone=!this.zonelessEnabled&&this.zoneIsDefined&&(D(Lc,{optional:!0})??!1);cancelScheduledCallback=null;useMicrotaskScheduler=!1;runningTick=!1;pendingRenderTaskId=null;constructor(){this.subscriptions.add(this.appRef.afterTick.subscribe(()=>{let e=this.taskService.add();if(!this.runningTick&&(this.cleanup(),!this.zonelessEnabled||this.appRef.includeAllTestViews)){this.taskService.remove(e);return}this.switchToMicrotaskScheduler(),this.taskService.remove(e)})),this.subscriptions.add(this.ngZone.onUnstable.subscribe(()=>{this.runningTick||this.cleanup()}))}switchToMicrotaskScheduler(){this.ngZone.runOutsideAngular(()=>{let e=this.taskService.add();this.useMicrotaskScheduler=!0,queueMicrotask(()=>{this.useMicrotaskScheduler=!1,this.taskService.remove(e)})})}notify(e){if(!this.zonelessEnabled&&e===5)return;switch(e){case 0:case 2:{this.appRef.dirtyFlags|=2;break}case 3:case 4:case 5:case 1:{this.appRef.dirtyFlags|=4;break}case 6:{this.appRef.dirtyFlags|=2;break}case 12:{this.appRef.dirtyFlags|=16;break}case 13:{this.appRef.dirtyFlags|=2;break}case 11:break;default:this.appRef.dirtyFlags|=8}if(this.appRef.tracingSnapshot=this.tracing?.snapshot(this.appRef.tracingSnapshot)??null,!this.shouldScheduleTick())return;let o=this.useMicrotaskScheduler?J_:Mg;this.pendingRenderTaskId=this.taskService.add(),this.scheduleInRootZone?this.cancelScheduledCallback=Zone.root.run(()=>o(()=>this.tick())):this.cancelScheduledCallback=this.ngZone.runOutsideAngular(()=>o(()=>this.tick()))}shouldScheduleTick(){return!(this.appRef.destroyed||this.pendingRenderTaskId!==null||this.runningTick||this.appRef._runningTick||!this.zonelessEnabled&&this.zoneIsDefined&&Zone.current.get(ia+this.angularZoneId))}tick(){if(this.runningTick||this.appRef.destroyed)return;if(this.appRef.dirtyFlags===0){this.cleanup();return}!this.zonelessEnabled&&this.appRef.dirtyFlags&7&&(this.appRef.dirtyFlags|=1);let e=this.taskService.add();try{this.ngZone.run(()=>{this.runningTick=!0,this.appRef._tick()},void 0,this.schedulerTickApplyArgs)}catch(o){this.applicationErrorHandler(o)}finally{this.taskService.remove(e),this.cleanup()}}ngOnDestroy(){this.subscriptions.unsubscribe(),this.cleanup()}cleanup(){if(this.runningTick=!1,this.cancelScheduledCallback?.(),this.cancelScheduledCallback=null,this.pendingRenderTaskId!==null){let e=this.pendingRenderTaskId;this.pendingRenderTaskId=null,this.taskService.remove(e)}}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function jM(){return[{provide:bn,useExisting:VM},{provide:ge,useClass:ra},{provide:ar,useValue:!0}]}var qp=(()=>{class n{compileModuleSync(e){return new ll(e)}compileModuleAsync(e){return Promise.resolve(this.compileModuleSync(e))}clearCache(){}clearCacheFor(e){}getModuleId(e){}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function Qw(){return typeof $localize<"u"&&$localize.locale||ja}var Ba=new B("",{factory:()=>D(Ba,{optional:!0,skipSelf:!0})||Qw()});function I(n,t){return Ts(n,t?.equal)}function de(n){return Nm(n)}var $M=class n extends Error{_brand;constructor(t){super(t)}static IDLE=new n("IDLE");static LOADING=new n("LOADING")},Jw=n=>n;function Wp(n,t){if(typeof n=="function"){let e=cu(n,Jw,t?.equal);return zM(e,t?.debugName,t?.set)}else{let e=cu(n.source,n.computation,n.equal);return zM(e,n.debugName,n.set)}}function zM(n,t,e){let o=n[nt],i=n;if(e!==void 0){let r=a=>lu(o,a);i.set=a=>e(a,r),i.update=a=>e(a(de(n)),r)}else i.set=r=>lu(o,r),i.update=r=>Am(o,r);return i.asReadonly=Rc.bind(n),i}var UM=Symbol("InputSignalNode#UNSET"),fS=F(S({},Ns),{transformFn:void 0,applyValueToInputSignal(n,t){Wo(n,t)}});function GM(n,t){let e=Object.create(fS);e.value=n,e.transformFn=t?.transform;function o(){if(Ho(e),e.value===UM){let i=null;throw new z(-950,i)}return e.value}return o[nt]=e,o}var Tl=class{attributeName;constructor(t){this.attributeName=t}__NG_ELEMENT_ID__=()=>Ra(this.attributeName);toString(){return`HostAttributeToken ${this.attributeName}`}};function Yp(n){return mS(n)?n.default:n}function mS(n){return n&&typeof n=="object"&&"default"in n}function BM(n,t){return GM(n,t)}function _S(n){return GM(UM,n)}var Al=(BM.required=_S,BM);var hS=1e4;var EU=hS-1e3;var oo=(()=>{class n{static __NG_ELEMENT_ID__=CS}return n})();function CS(n){return MS(tt(),Z(),(n&16)===16)}function MS(n,t,e){if(Yn(n)&&!e){let o=Gt(n.index,t);return new So(o,o)}else if(n.type&175){let o=t[xt];return new So(o,t)}return null}var bS=(()=>{class n{zone=D(ge);changeDetectionScheduler=D(bn);applicationRef=D(Xn);applicationErrorHandler=D(Nt);_onMicrotaskEmptySubscription;initialize(){this._onMicrotaskEmptySubscription||(this._onMicrotaskEmptySubscription=this.zone.onMicrotaskEmpty.subscribe({next:()=>{this.changeDetectionScheduler.runningTick||this.zone.run(()=>{try{this.applicationRef.dirtyFlags|=1,this.applicationRef._tick()}catch(e){this.applicationErrorHandler(e)}})}}))}ngOnDestroy(){this._onMicrotaskEmptySubscription?.unsubscribe()}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),PS=new B("",{factory:()=>!1});function OS({ngZoneFactory:n,scheduleInRootZone:t}){return n??=()=>new ge(F(S({},WM()),{scheduleInRootZone:t})),[{provide:ar,useValue:!1},{provide:ge,useFactory:n},{provide:ri,multi:!0,useFactory:()=>{let e=D(bS,{optional:!0});return()=>e.initialize()}},{provide:ri,multi:!0,useFactory:()=>{let e=D(vS);return()=>{e.initialize()}}},{provide:Lc,useValue:t??Cg}]}function qM(n){let t=n?.scheduleInRootZone,e=OS({ngZoneFactory:()=>{let o=WM(n);return o.scheduleInRootZone=t,o.shouldCoalesceEventChangeDetection&&An("NgZone_CoalesceEvent"),new ge(o)},scheduleInRootZone:t});return bo([{provide:PS,useValue:!0},e])}function WM(n){return{enableLongStackTrace:!1,shouldCoalesceEventChangeDetection:n?.eventCoalescing??!1,shouldCoalesceRunChangeDetection:n?.runCoalescing??!1}}var vS=(()=>{class n{subscription=new Be;initialized=!1;zone=D(ge);pendingTasks=D(kn);initialize(){if(this.initialized)return;this.initialized=!0;let e=null;!this.zone.isStable&&!this.zone.hasPendingMacrotasks&&!this.zone.hasPendingMicrotasks&&(e=this.pendingTasks.add()),this.zone.runOutsideAngular(()=>{this.subscription.add(this.zone.onStable.subscribe(()=>{ge.assertNotInAngularZone(),queueMicrotask(()=>{e!==null&&!this.zone.hasPendingMacrotasks&&!this.zone.hasPendingMicrotasks&&(this.pendingTasks.remove(e),e=null)})}))}),this.subscription.add(this.zone.onUnstable.subscribe(()=>{ge.assertInAngularZone(),e??=this.pendingTasks.add()}))}ngOnDestroy(){this.subscription.unsubscribe()}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();var Kp=new B(""),yS=new B("");function Ha(n){return!n.moduleRef}function xS(n){let t=Ha(n)?n.r3Injector:n.moduleRef.injector,e=t.get(ge);return e.run(()=>{Ha(n)?n.r3Injector.resolveInjectorInitializers():n.moduleRef.resolveInjectorInitializers();let o=t.get(Nt),i;if(e.runOutsideAngular(()=>{i=e.onError.subscribe({next:o})}),Ha(n)){let r=()=>t.destroy(),a=n.platformInjector.get(Kp);a.add(r),t.onDestroy(()=>{i.unsubscribe(),a.delete(r)})}else{let r=()=>n.moduleRef.destroy(),a=n.platformInjector.get(Kp);a.add(r),n.moduleRef.onDestroy(()=>{wa(n.allPlatformModules,n.moduleRef),i.unsubscribe(),a.delete(r)})}return SS(o,e,()=>{let r=t.get(kn),a=r.add(),s=t.get(Vp);return s.runInitializers(),s.donePromise.then(()=>{let c=t.get(Ba,ja);if(vM(c||ja),!t.get(yS,!0))return Ha(n)?t.get(Xn):(n.allPlatformModules.push(n.moduleRef),n.moduleRef);if(Ha(n)){let f=t.get(Xn);return n.rootComponent!==void 0&&f.bootstrap(n.rootComponent),f}else return wS?.(n.moduleRef,n.allPlatformModules),n.moduleRef}).finally(()=>{r.remove(a)})})})}var wS;function SS(n,t,e){try{let o=e();return Eo(o)?o.catch(i=>{throw t.runOutsideAngular(()=>n(i)),i}):o}catch(o){throw t.runOutsideAngular(()=>n(o)),o}}var Il=null;function kS(n=[],t){return Ct.create({name:t,providers:[{provide:ga,useValue:"platform"},{provide:Kp,useValue:new Set([()=>Il=null])},...n]})}function ES(n=[]){if(Il)return Il;let t=kS(n);return Il=t,PM(),DS(t),t}function DS(n){let t=n.get(Fc,null);et(n,()=>{t?.forEach(e=>e())})}function KM(n){let{rootComponent:t,appProviders:e,platformProviders:o,platformRef:i}=n;Ce(pe.BootstrapApplicationStart);try{let r=i?.injector??ES(o),a=[jM(),eh,...e||[]],s=new Ta({providers:a,parent:r,debugName:"",runEnvironmentInitializers:!1});return xS({r3Injector:s.injector,platformInjector:r,rootComponent:t})}catch(r){return Promise.reject(r)}finally{Ce(pe.BootstrapApplicationEnd)}}function Si(n){return typeof n=="boolean"?n:n!=null&&n!=="false"}var YM=null;function Kt(){return YM}function Zp(n){YM??=n}var Ua=class{},Cr=(()=>{class n{historyGo(e){throw new Error("")}static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:()=>D(ZM),providedIn:"platform"})}return n})();var ZM=(()=>{class n extends Cr{_location;_history;_doc=D(Ue);constructor(){super(),this._location=window.location,this._history=window.history}getBaseHrefFromDOM(){return Kt().getBaseHref(this._doc)}onPopState(e){let o=Kt().getGlobalEventTarget(this._doc,"window");return o.addEventListener("popstate",e,!1),()=>o.removeEventListener("popstate",e)}onHashChange(e){let o=Kt().getGlobalEventTarget(this._doc,"window");return o.addEventListener("hashchange",e,!1),()=>o.removeEventListener("hashchange",e)}get href(){return this._location.href}get protocol(){return this._location.protocol}get hostname(){return this._location.hostname}get port(){return this._location.port}get pathname(){return this._location.pathname}get search(){return this._location.search}get hash(){return this._location.hash}set pathname(e){this._location.pathname=e}pushState(e,o,i){this._history.pushState(e,o,i)}replaceState(e,o,i){this._history.replaceState(e,o,i)}forward(){this._history.forward()}back(){this._history.back()}historyGo(e=0){this._history.go(e)}getState(){return this._history.state}static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:()=>new n,providedIn:"platform"})}return n})();function XM(n,t){return n?t?n.endsWith("/")?t.startsWith("/")?n+t.slice(1):n+t:t.startsWith("/")?n+t:`${n}/${t}`:n:t}function QM(n){let t=n.search(/#|\?|$/);return n[t-1]==="/"?n.slice(0,t-1)+n.slice(t):n}function Ao(n){return n&&n[0]!=="?"?`?${n}`:n}var Mr=(()=>{class n{historyGo(e){throw new Error("")}static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:()=>D(TS),providedIn:"root"})}return n})(),IS=new B(""),TS=(()=>{class n extends Mr{_platformLocation;_baseHref;_removeListenerFns=[];constructor(e,o){super(),this._platformLocation=e,this._baseHref=o??this._platformLocation.getBaseHrefFromDOM()??D(Ue).location?.origin??""}ngOnDestroy(){for(;this._removeListenerFns.length;)this._removeListenerFns.pop()()}onPopState(e){this._removeListenerFns.push(this._platformLocation.onPopState(e),this._platformLocation.onHashChange(e))}getBaseHref(){return this._baseHref}prepareExternalUrl(e){return XM(this._baseHref,e)}path(e=!1){let o=this._platformLocation.pathname+Ao(this._platformLocation.search),i=this._platformLocation.hash;return i&&e?`${o}${i}`:o}pushState(e,o,i,r){let a=this.prepareExternalUrl(i+Ao(r));this._platformLocation.pushState(e,o,a)}replaceState(e,o,i,r){let a=this.prepareExternalUrl(i+Ao(r));this._platformLocation.replaceState(e,o,a)}forward(){this._platformLocation.forward()}back(){this._platformLocation.back()}getState(){return this._platformLocation.getState()}historyGo(e=0){this._platformLocation.historyGo?.(e)}static \u0275fac=function(o){return new(o||n)(Q(Cr),Q(IS,8))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})();var br=(()=>{class n{_subject=new ye;_basePath;_locationStrategy;_urlChangeListeners=[];_urlChangeSubscription=null;constructor(e){this._locationStrategy=e;let o=this._locationStrategy.getBaseHref();this._basePath=RS(QM(JM(o))),this._locationStrategy.onPopState(i=>{this._subject.next({url:this.path(!0),pop:!0,state:i.state,type:i.type})})}ngOnDestroy(){this._urlChangeSubscription?.unsubscribe(),this._urlChangeListeners=[]}path(e=!1){return this.normalize(this._locationStrategy.path(e))}getState(){return this._locationStrategy.getState()}isCurrentPathEqualTo(e,o=""){return this.path()==this.normalize(e+Ao(o))}normalize(e){return n.stripTrailingSlash(NS(this._basePath,JM(e)))}prepareExternalUrl(e){return e&&e[0]!=="/"&&(e="/"+e),this._locationStrategy.prepareExternalUrl(e)}go(e,o="",i=null){this._locationStrategy.pushState(i,"",e,o),this._notifyUrlChangeListeners(this.prepareExternalUrl(e+Ao(o)),i)}replaceState(e,o="",i=null){this._locationStrategy.replaceState(i,"",e,o),this._notifyUrlChangeListeners(this.prepareExternalUrl(e+Ao(o)),i)}forward(){this._locationStrategy.forward()}back(){this._locationStrategy.back()}historyGo(e=0){this._locationStrategy.historyGo?.(e)}onUrlChange(e){return this._urlChangeListeners.push(e),this._urlChangeSubscription??=this.subscribe(o=>{this._notifyUrlChangeListeners(o.url,o.state)}),()=>{let o=this._urlChangeListeners.indexOf(e);this._urlChangeListeners.splice(o,1),this._urlChangeListeners.length===0&&(this._urlChangeSubscription?.unsubscribe(),this._urlChangeSubscription=null)}}_notifyUrlChangeListeners(e="",o){this._urlChangeListeners.forEach(i=>i(e,o))}subscribe(e,o,i){return this._subject.subscribe({next:e,error:o??void 0,complete:i??void 0})}static normalizeQueryParams=Ao;static joinWithSlash=XM;static stripTrailingSlash=QM;static \u0275fac=function(o){return new(o||n)(Q(Mr))};static \u0275prov=te({token:n,factory:()=>AS(),providedIn:"root"})}return n})();function AS(){return new br(Q(Mr))}function NS(n,t){if(!n||!t.startsWith(n))return t;let e=t.substring(n.length);return e===""||["/",";","?","#"].includes(e[0])?e:t}function JM(n){return n.replace(/\/index\.html$/,"")}function RS(n){if(new RegExp("^(https?:)?//").test(n)){let[,e]=n.split(/\/\/[^\/]+/);return e}return n}var gt=(function(n){return n[n.Format=0]="Format",n[n.Standalone=1]="Standalone",n})(gt||{}),ve=(function(n){return n[n.Narrow=0]="Narrow",n[n.Abbreviated=1]="Abbreviated",n[n.Wide=2]="Wide",n[n.Short=3]="Short",n})(ve||{}),Dt=(function(n){return n[n.Short=0]="Short",n[n.Medium=1]="Medium",n[n.Long=2]="Long",n[n.Full=3]="Full",n})(Dt||{}),ro={Decimal:0,Group:1,List:2,PercentSign:3,PlusSign:4,MinusSign:5,Exponential:6,SuperscriptingExponent:7,PerMille:8,Infinity:9,NaN:10,TimeSeparator:11,CurrencyDecimal:12,CurrencyGroup:13};function n0(n){return Lt(n)[Ze.LocaleId]}function o0(n,t,e){let o=Lt(n),i=[o[Ze.DayPeriodsFormat],o[Ze.DayPeriodsStandalone]],r=Yt(i,t);return Yt(r,e)}function i0(n,t,e){let o=Lt(n),i=[o[Ze.DaysFormat],o[Ze.DaysStandalone]],r=Yt(i,t);return Yt(r,e)}function r0(n,t,e){let o=Lt(n),i=[o[Ze.MonthsFormat],o[Ze.MonthsStandalone]],r=Yt(i,t);return Yt(r,e)}function a0(n,t){let o=Lt(n)[Ze.Eras];return Yt(o,t)}function Ga(n,t){let e=Lt(n);return Yt(e[Ze.DateFormat],t)}function qa(n,t){let e=Lt(n);return Yt(e[Ze.TimeFormat],t)}function Wa(n,t){let o=Lt(n)[Ze.DateTimeFormat];return Yt(o,t)}function Ka(n,t){let e=Lt(n),o=e[Ze.NumberSymbols][t];if(typeof o>"u"){if(t===ro.CurrencyDecimal)return e[Ze.NumberSymbols][ro.Decimal];if(t===ro.CurrencyGroup)return e[Ze.NumberSymbols][ro.Group]}return o}function s0(n){if(!n[Ze.ExtraData])throw new z(2303,!1)}function c0(n){let t=Lt(n);return s0(t),(t[Ze.ExtraData][2]||[]).map(o=>typeof o=="string"?Qp(o):[Qp(o[0]),Qp(o[1])])}function l0(n,t,e){let o=Lt(n);s0(o);let i=[o[Ze.ExtraData][0],o[Ze.ExtraData][1]],r=Yt(i,t)||[];return Yt(r,e)||[]}function Yt(n,t){for(let e=t;e>-1;e--)if(typeof n[e]<"u")return n[e];throw new z(2304,!1)}function Qp(n){let[t,e]=n.split(":");return{hours:+t,minutes:+e}}var FS=/^(\d{4,})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/,Nl=Object.create(null),LS=/((?:[^BEGHLMOSWYZabcdhmswyz']+)|(?:'(?:[^']|'')*')|(?:G{1,5}|y{1,4}|Y{1,4}|M{1,5}|L{1,5}|w{1,2}|W{1}|d{1,2}|E{1,6}|c{1,6}|a{1,5}|b{1,5}|B{1,5}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|S{1,3}|z{1,4}|Z{1,5}|O{1,4}))([\s\S]*)/,VS=256;function d0(n,t,e,o){let i=KS(n);jS(t),t=io(e,t)||t;let a=[],s;for(;t;)if(s=LS.exec(t),s){a=a.concat(s.slice(1));let f=a.pop();if(!f)break;t=f}else{a.push(t);break}let c=i.getTimezoneOffset();o&&(c=g0(o,c),i=WS(i,o));let p="";return a.forEach(f=>{let _=GS(f);p+=_?_(i,e,c):f==="''"?"'":f.replace(/(^'|'$)/g,"").replace(/''/g,"'")}),p}function jS(n){if(n.length>VS)throw new z(2300,!1)}function jl(n,t,e){let o=new Date(0);return o.setFullYear(n,t,e),o.setHours(0,0,0),o}function io(n,t){let e=n0(n);if(Nl[e]??=Object.create(null),Nl[e][t])return Nl[e][t];let o="";switch(t){case"shortDate":o=Ga(n,Dt.Short);break;case"mediumDate":o=Ga(n,Dt.Medium);break;case"longDate":o=Ga(n,Dt.Long);break;case"fullDate":o=Ga(n,Dt.Full);break;case"shortTime":o=qa(n,Dt.Short);break;case"mediumTime":o=qa(n,Dt.Medium);break;case"longTime":o=qa(n,Dt.Long);break;case"fullTime":o=qa(n,Dt.Full);break;case"short":let i=io(n,"shortTime"),r=io(n,"shortDate");o=Rl(Wa(n,Dt.Short),[i,r]);break;case"medium":let a=io(n,"mediumTime"),s=io(n,"mediumDate");o=Rl(Wa(n,Dt.Medium),[a,s]);break;case"long":let c=io(n,"longTime"),p=io(n,"longDate");o=Rl(Wa(n,Dt.Long),[c,p]);break;case"full":let f=io(n,"fullTime"),_=io(n,"fullDate");o=Rl(Wa(n,Dt.Full),[f,_]);break}return o&&(Nl[e][t]=o),o}function Rl(n,t){return t&&(n=n.replace(/\{([^}]+)}/g,function(e,o){return Object.hasOwn(t,o)?t[o]:e})),n}function pn(n,t,e="-",o,i){let r="";(n<0||i&&n<=0)&&(i?n=-n+1:(n=-n,r=e));let a=String(n);for(;a.length0||s>-e)&&(s+=e),n===3)s===0&&e===-12&&(s=12);else if(n===6)return $S(s,t);let c=Ka(a,ro.MinusSign);return pn(s,t,c,o,i)}}function zS(n,t){switch(n){case 0:return t.getFullYear();case 1:return t.getMonth();case 2:return t.getDate();case 3:return t.getHours();case 4:return t.getMinutes();case 5:return t.getSeconds();case 6:return t.getMilliseconds();case 7:return t.getDay();default:throw new z(2301,!1)}}function Te(n,t,e=gt.Format,o=!1){return function(i,r){return BS(i,r,n,t,e,o)}}function BS(n,t,e,o,i,r){switch(e){case 2:return r0(t,i,o)[n.getMonth()];case 1:return i0(t,i,o)[n.getDay()];case 0:let a=n.getHours(),s=n.getMinutes();if(r){let p=c0(t),f=l0(t,i,o),_=p.findIndex(C=>{if(Array.isArray(C)){let[h,P]=C,T=a>=h.hours&&s>=h.minutes,A=a0?Math.floor(i/60):Math.ceil(i/60);switch(n){case 0:return(i>=0?"+":"")+pn(a,2,r)+pn(Math.abs(i%60),2,r);case 1:return"GMT"+(i>=0?"+":"")+pn(a,1,r);case 2:return"GMT"+(i>=0?"+":"")+pn(a,2,r)+":"+pn(Math.abs(i%60),2,r);case 3:return o===0?"Z":(i>=0?"+":"")+pn(a,2,r)+":"+pn(Math.abs(i%60),2,r);default:throw new z(2310,!1)}}}var HS=0,Vl=4;function US(n){let t=jl(n,HS,1).getDay();return jl(n,0,1+(t<=Vl?Vl:Vl+7)-t)}function u0(n){let t=n.getDay(),e=t===0?-3:Vl-t;return jl(n.getFullYear(),n.getMonth(),n.getDate()+e)}function Jp(n,t=!1){return function(e,o){let i;if(t){let r=new Date(e.getFullYear(),e.getMonth(),1).getDay()-1,a=e.getDate();i=1+Math.floor((a+r)/7)}else{let r=u0(e),a=US(r.getFullYear()),s=r.getTime()-a.getTime();i=1+Math.round(s/6048e5)}return pn(i,n,Ka(o,ro.MinusSign))}}function Ll(n,t=!1){return function(e,o){let r=u0(e).getFullYear();return pn(r,n,Ka(o,ro.MinusSign),t)}}var Xp=Object.create(null);function GS(n){if(Xp[n])return Xp[n];let t;switch(n){case"G":case"GG":case"GGG":t=Te(3,ve.Abbreviated);break;case"GGGG":t=Te(3,ve.Wide);break;case"GGGGG":t=Te(3,ve.Narrow);break;case"y":t=Qe(0,1,0,!1,!0);break;case"yy":t=Qe(0,2,0,!0,!0);break;case"yyy":t=Qe(0,3,0,!1,!0);break;case"yyyy":t=Qe(0,4,0,!1,!0);break;case"Y":t=Ll(1);break;case"YY":t=Ll(2,!0);break;case"YYY":t=Ll(3);break;case"YYYY":t=Ll(4);break;case"M":case"L":t=Qe(1,1,1);break;case"MM":case"LL":t=Qe(1,2,1);break;case"MMM":t=Te(2,ve.Abbreviated);break;case"MMMM":t=Te(2,ve.Wide);break;case"MMMMM":t=Te(2,ve.Narrow);break;case"LLL":t=Te(2,ve.Abbreviated,gt.Standalone);break;case"LLLL":t=Te(2,ve.Wide,gt.Standalone);break;case"LLLLL":t=Te(2,ve.Narrow,gt.Standalone);break;case"w":t=Jp(1);break;case"ww":t=Jp(2);break;case"W":t=Jp(1,!0);break;case"d":t=Qe(2,1);break;case"dd":t=Qe(2,2);break;case"c":case"cc":t=Qe(7,1);break;case"ccc":t=Te(1,ve.Abbreviated,gt.Standalone);break;case"cccc":t=Te(1,ve.Wide,gt.Standalone);break;case"ccccc":t=Te(1,ve.Narrow,gt.Standalone);break;case"cccccc":t=Te(1,ve.Short,gt.Standalone);break;case"E":case"EE":case"EEE":t=Te(1,ve.Abbreviated);break;case"EEEE":t=Te(1,ve.Wide);break;case"EEEEE":t=Te(1,ve.Narrow);break;case"EEEEEE":t=Te(1,ve.Short);break;case"a":case"aa":case"aaa":t=Te(0,ve.Abbreviated);break;case"aaaa":t=Te(0,ve.Wide);break;case"aaaaa":t=Te(0,ve.Narrow);break;case"b":case"bb":case"bbb":t=Te(0,ve.Abbreviated,gt.Standalone,!0);break;case"bbbb":t=Te(0,ve.Wide,gt.Standalone,!0);break;case"bbbbb":t=Te(0,ve.Narrow,gt.Standalone,!0);break;case"B":case"BB":case"BBB":t=Te(0,ve.Abbreviated,gt.Format,!0);break;case"BBBB":t=Te(0,ve.Wide,gt.Format,!0);break;case"BBBBB":t=Te(0,ve.Narrow,gt.Format,!0);break;case"h":t=Qe(3,1,-12);break;case"hh":t=Qe(3,2,-12);break;case"H":t=Qe(3,1);break;case"HH":t=Qe(3,2);break;case"m":t=Qe(4,1);break;case"mm":t=Qe(4,2);break;case"s":t=Qe(5,1);break;case"ss":t=Qe(5,2);break;case"S":t=Qe(6,1);break;case"SS":t=Qe(6,2);break;case"SSS":t=Qe(6,3);break;case"Z":case"ZZ":case"ZZZ":t=Fl(0);break;case"ZZZZZ":t=Fl(3);break;case"O":case"OO":case"OOO":case"z":case"zz":case"zzz":t=Fl(1);break;case"OOOO":case"ZZZZ":case"zzzz":t=Fl(2);break;default:return null}return Xp[n]=t,t}function g0(n,t){n=n.replace(/:/g,"");let e=Date.parse("Jan 01, 1970 00:00:00 "+n)/6e4;return isNaN(e)?t:e}function qS(n,t){return n=new Date(n.getTime()),n.setMinutes(n.getMinutes()+t),n}function WS(n,t,e){let i=n.getTimezoneOffset(),r=g0(t,i);return qS(n,-1*(r-i))}function KS(n){if(e0(n))return n;if(typeof n=="number"&&!isNaN(n))return new Date(n);if(typeof n=="string"){if(n=n.trim(),/^(\d{4}(-\d{1,2}(-\d{1,2})?)?)$/.test(n)){let[i,r=1,a=1]=n.split("-").map(s=>+s);return jl(i,r-1,a)}let e=parseFloat(n);if(!isNaN(n-e))return new Date(e);let o;if(o=n.match(FS))return YS(o)}let t=new Date(n);if(!e0(t))throw new z(2311,!1);return t}function YS(n){let t=new Date(0),e=0,o=0,i=n[8]?t.setUTCFullYear:t.setFullYear,r=n[8]?t.setUTCHours:t.setHours;n[9]&&(e=Number(n[9]+n[10]),o=Number(n[9]+n[11])),i.call(t,Number(n[1]),Number(n[2])-1,Number(n[3]));let a=Number(n[4]||0)-e,s=Number(n[5]||0)-o,c=Number(n[6]||0),p=Math.floor(parseFloat("0."+(n[7]||0))*1e3);return r.call(t,a,s,c,p),t}function e0(n){return n instanceof Date&&!isNaN(n.valueOf())}var ef=/\s+/,t0=[],ki=(()=>{class n{_ngEl;_renderer;initialClasses=t0;rawClass;stateMap=new Map;constructor(e,o){this._ngEl=e,this._renderer=o}set klass(e){this.initialClasses=e!=null?e.trim().split(ef):t0}set ngClass(e){this.rawClass=typeof e=="string"?e.trim().split(ef):e}ngDoCheck(){for(let o of this.initialClasses)this._updateState(o,!0);let e=this.rawClass;if(Array.isArray(e)||e instanceof Set)for(let o of e)this._updateState(o,!0);else if(e!=null)for(let o of Object.keys(e))this._updateState(o,!!e[o]);this._applyStateDiff()}_updateState(e,o){let i=this.stateMap.get(e);i!==void 0?(i.enabled!==o&&(i.changed=!0,i.enabled=o),i.touched=!0):this.stateMap.set(e,{enabled:o,changed:!0,touched:!0})}_applyStateDiff(){for(let e of this.stateMap){let o=e[0],i=e[1];i.changed?(this._toggleClass(o,i.enabled),i.changed=!1):i.touched||(i.enabled&&this._toggleClass(o,!1),this.stateMap.delete(o)),i.touched=!1}}_toggleClass(e,o){e=e.trim(),e.length>0&&e.split(ef).forEach(i=>{o?this._renderer.addClass(this._ngEl.nativeElement,i):this._renderer.removeClass(this._ngEl.nativeElement,i)})}static \u0275fac=function(o){return new(o||n)(le(Mt),le(wt))};static \u0275dir=qe({type:n,selectors:[["","ngClass",""]],inputs:{klass:[0,"class","klass"],ngClass:"ngClass"}})}return n})();function p0(n,t){return new z(2100,!1)}var Ya=(()=>{class n{transform(e){return e==null?null:(ZS(n,e),e.toUpperCase())}static \u0275fac=function(o){return new(o||n)};static \u0275pipe=Sl({name:"uppercase",type:n,pure:!0})}return n})();function ZS(n,t){if(typeof t!="string")throw p0(n,t)}var QS="mediumDate",f0=new B(""),m0=new B(""),Ei=(()=>{class n{locale;defaultTimezone;defaultOptions;constructor(e,o,i){this.locale=e,this.defaultTimezone=o,this.defaultOptions=i}transform(e,o,i,r){if(e==null||e===""||e!==e)return null;try{let a=o??this.defaultOptions?.dateFormat??QS,s=i??this.defaultOptions?.timezone??this.defaultTimezone??void 0;return d0(e,a,r||this.locale,s)}catch(a){throw p0(n,a.message)}}static \u0275fac=function(o){return new(o||n)(le(Ba,16),le(f0,24),le(m0,24))};static \u0275pipe=Sl({name:"date",type:n,pure:!0})}return n})();var No=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275mod=Jn({type:n});static \u0275inj=Pn({})}return n})();function Za(n,t){t=encodeURIComponent(t);for(let e of n.split(";")){let o=e.indexOf("="),[i,r]=o==-1?[e,""]:[e.slice(0,o),e.slice(o+1)];if(i.trim()!==t)continue;let a=r;try{a=decodeURIComponent(r)}catch(s){}return a.length>1&&a[0]==='"'&&a[a.length-1]==='"'&&(a=a.slice(1,-1)),a}return null}var JS=(()=>{class n{build(){return new XMLHttpRequest}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),tf=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:function(o){let i=null;return o?i=new(o||n):i=Q(JS),i},providedIn:"root"})}return n})();var _0="browser";var Qa=class{_doc;constructor(t){this._doc=t}manager},zl=(()=>{class n extends Qa{constructor(e){super(e)}supports(e){return!0}addEventListener(e,o,i,r){return e.addEventListener(o,i,r),()=>this.removeEventListener(e,o,i,r)}removeEventListener(e,o,i,r){return e.removeEventListener(o,i,r)}static \u0275fac=function(o){return new(o||n)(Q(Ue))};static \u0275prov=te({token:n,factory:n.\u0275fac})}return n})(),Ul=new B(""),af=(()=>{class n{_zone;_plugins;_eventNameToPlugin=new Map;constructor(e,o){this._zone=o,e.forEach(a=>{a.manager=this});let i=e.filter(a=>!(a instanceof zl));this._plugins=i.slice().reverse();let r=e.find(a=>a instanceof zl);r&&this._plugins.push(r)}addEventListener(e,o,i,r){return this._findPluginFor(o).addEventListener(e,o,i,r)}getZone(){return this._zone}_findPluginFor(e){let o=this._eventNameToPlugin.get(e);if(o)return o;if(o=this._plugins.find(r=>r.supports(e)),!o)throw new z(-5101,!1);return this._eventNameToPlugin.set(e,o),o}static \u0275fac=function(o){return new(o||n)(Q(Ul),Q(ge))};static \u0275prov=te({token:n,factory:n.\u0275fac})}return n})(),nf="ng-app-id";function h0(n){for(let t of n)t.remove()}function C0(n,t){let e=t.createElement("style");return e.textContent=n,e}function ek(n,t,e,o){let i=n.head?.querySelectorAll(`style[${nf}="${t}"],link[${nf}="${t}"]`);if(!i||i.length===0)return!1;for(let r of i)r.removeAttribute(nf),r instanceof HTMLLinkElement?o.set(r.href.slice(r.href.lastIndexOf("/")+1),{usage:0,elements:[r]}):r.textContent&&e.set(r.textContent,{usage:0,elements:[r]});return!0}function rf(n,t){let e=t.createElement("link");return e.setAttribute("rel","stylesheet"),e.setAttribute("href",n),e}var sf=(()=>{class n{doc;appId;nonce;inline=new Map;external=new Map;hosts=new Set;constructor(e,o,i,r={}){this.doc=e,this.appId=o,this.nonce=i,ek(e,o,this.inline,this.external)&&this.hosts.add(e.head)}addStyles(e,o){for(let i of e)this.addUsage(i,this.inline,C0);o?.forEach(i=>this.addUsage(i,this.external,rf))}removeStyles(e,o){for(let i of e)this.removeUsage(i,this.inline);o?.forEach(i=>this.removeUsage(i,this.external))}addUsage(e,o,i){let r=o.get(e);r?r.usage++:o.set(e,{usage:1,elements:[...this.hosts].map(a=>this.addElement(a,i(e,this.doc)))})}removeUsage(e,o){let i=o.get(e);i&&(i.usage--,i.usage<=0&&(h0(i.elements),o.delete(e)))}ngOnDestroy(){for(let[,{elements:e}]of[...this.inline,...this.external])h0(e);this.hosts.clear()}addHost(e){if(!this.hosts.has(e)){this.hosts.add(e);for(let[o,{elements:i}]of this.inline)i.push(this.addElement(e,C0(o,this.doc)));for(let[o,{elements:i}]of this.external)i.push(this.addElement(e,rf(o,this.doc)))}}removeHost(e){this.hosts.delete(e);for(let o of[...this.inline.values(),...this.external.values()]){let i=[];for(let r of o.elements)r.parentNode===e?r.remove():i.push(r);o.elements=i}}addElement(e,o){return this.nonce&&o.setAttribute("nonce",this.nonce),e.appendChild(o)}static \u0275fac=function(o){return new(o||n)(Q(Ue),Q(ha),Q(Ma,8),Q(Ca))};static \u0275prov=te({token:n,factory:n.\u0275fac})}return n})(),of={svg:"http://www.w3.org/2000/svg",xhtml:"http://www.w3.org/1999/xhtml",xlink:"http://www.w3.org/1999/xlink",xml:"http://www.w3.org/XML/1998/namespace",xmlns:"http://www.w3.org/2000/xmlns/",math:"http://www.w3.org/1998/Math/MathML"},cf=/%COMP%/g;var b0="%COMP%",tk=`_nghost-${b0}`,nk=`_ngcontent-${b0}`,ok=!0,ik=new B("",{factory:()=>ok}),rk=new B("");function ak(n){return nk.replace(cf,n)}function sk(n){return tk.replace(cf,n)}function P0(n,t){return t.map(e=>e.replace(cf,n))}var lf=(()=>{class n{eventManager;sharedStylesHost;appId;removeStylesOnCompDestroy;doc;ngZone;nonce;tracingService;rendererByCompId=new Map;defaultRenderer;cssVarNamespace;constructor(e,o,i,r,a,s,c=null,p=null,f=null){this.eventManager=e,this.sharedStylesHost=o,this.appId=i,this.removeStylesOnCompDestroy=r,this.doc=a,this.ngZone=s,this.nonce=c,this.tracingService=p,this.cssVarNamespace=f??"",this.defaultRenderer=new Ja(e,a,s,this.tracingService,this.cssVarNamespace)}createRenderer(e,o){if(!e||!o)return this.defaultRenderer;let i=this.getOrCreateRenderer(e,o);return i instanceof Hl?i.applyToHost(e):i instanceof Xa&&i.applyStyles(),i}getOrCreateRenderer(e,o){let i=this.rendererByCompId,r=i.get(o.id);if(!r){let a=this.doc,s=this.ngZone,c=this.eventManager,p=this.sharedStylesHost,f=this.removeStylesOnCompDestroy,_=this.tracingService;switch(o.encapsulation){case un.Emulated:r=new Hl(c,p,o,this.appId,f,a,s,_,this.cssVarNamespace);break;case un.ShadowDom:return new Bl(c,e,o,a,s,this.nonce,_,this.cssVarNamespace,p);case un.ExperimentalIsolatedShadowDom:return new Bl(c,e,o,a,s,this.nonce,_,this.cssVarNamespace);default:r=new Xa(c,p,o,f,a,s,_,this.cssVarNamespace);break}i.set(o.id,r)}return r}ngOnDestroy(){this.rendererByCompId.clear()}componentReplaced(e){this.rendererByCompId.delete(e)}static \u0275fac=function(o){return new(o||n)(Q(af),Q(Oi),Q(ha),Q(ik),Q(Ue),Q(ge),Q(Ma),Q(Tn,8),Q(rk,8))};static \u0275prov=te({token:n,factory:n.\u0275fac})}return n})(),Ja=class{eventManager;doc;ngZone;tracingService;cssVarNamespace;data=Object.create(null);throwOnSyntheticProps=!0;constructor(t,e,o,i,r=""){this.eventManager=t,this.doc=e,this.ngZone=o,this.tracingService=i,this.cssVarNamespace=r}destroy(){}destroyNode=null;createElement(t,e){return e?this.doc.createElementNS(of[e]||e,t):this.doc.createElement(t)}createComment(t){return this.doc.createComment(t)}createText(t){return this.doc.createTextNode(t)}appendChild(t,e){(M0(t)?t.content:t).appendChild(e)}insertBefore(t,e,o){if(t){let i=M0(t)?t.content:t;if(o!=null&&o.parentNode!==i)throw new z(-5106,!1);i.insertBefore(e,o)}}removeChild(t,e){e.remove()}selectRootElement(t,e){let o=typeof t=="string"?this.doc.querySelector(t):t;if(!o)throw new z(-5104,!1);return e||(o.textContent=""),o}parentNode(t){return t.parentNode}nextSibling(t){return t.nextSibling}setAttribute(t,e,o,i){if(i){e=i+":"+e;let r=of[i];r?t.setAttributeNS(r,e,o):t.setAttribute(e,o)}else t.setAttribute(e,o)}removeAttribute(t,e,o){if(o){let i=of[o];i?t.removeAttributeNS(i,e):t.removeAttribute(`${o}:${e}`)}else t.removeAttribute(e)}addClass(t,e){t.classList.add(e)}removeClass(t,e){t.classList.remove(e)}setStyle(t,e,o,i){let r=e.startsWith("--");r&&(e=e.replace("%NS%",this.cssVarNamespace)),r||i&(Dn.DashCase|Dn.Important)?t.style.setProperty(e,o,i&Dn.Important?"important":""):t.style[e]=o}removeStyle(t,e,o){let i=e.startsWith("--");i&&(e=e.replace("%NS%",this.cssVarNamespace)),i||o&Dn.DashCase?t.style.removeProperty(e):t.style[e]=""}setProperty(t,e,o){t!=null&&(t[e]=o)}setValue(t,e){t.nodeValue=e}listen(t,e,o,i){if(typeof t=="string"&&(t=Kt().getGlobalEventTarget(this.doc,t),!t))throw new z(-5102,!1);let r=this.decoratePreventDefault(o);return this.tracingService?.wrapEventListener&&(r=this.tracingService.wrapEventListener(t,e,r)),this.eventManager.addEventListener(t,e,r,i)}decoratePreventDefault(t){return e=>{if(e==="__ngUnwrap__")return t;t(e)===!1&&e.preventDefault()}}};function M0(n){return n.tagName==="TEMPLATE"&&n.content!==void 0}var Bl=class extends Ja{hostEl;sharedStylesHost;shadowRoot;constructor(t,e,o,i,r,a,s,c,p){super(t,i,r,s,c),this.hostEl=e,this.sharedStylesHost=p,this.shadowRoot=e.attachShadow({mode:"open"}),this.sharedStylesHost&&this.sharedStylesHost.addHost(this.shadowRoot);let f=o.styles;f=P0(o.id,f).map(C=>C.replace(/%NS%/g,c));for(let C of f){let h=document.createElement("style");a&&h.setAttribute("nonce",a),h.textContent=C,this.shadowRoot.appendChild(h)}let _=o.getExternalStyles?.();if(_)for(let C of _){let h=rf(C,i);a&&h.setAttribute("nonce",a),this.shadowRoot.appendChild(h)}}nodeOrShadowRoot(t){return t===this.hostEl?this.shadowRoot:t}appendChild(t,e){return super.appendChild(this.nodeOrShadowRoot(t),e)}insertBefore(t,e,o){return super.insertBefore(this.nodeOrShadowRoot(t),e,o)}removeChild(t,e){return super.removeChild(null,e)}parentNode(t){return this.nodeOrShadowRoot(super.parentNode(this.nodeOrShadowRoot(t)))}destroy(){this.sharedStylesHost&&this.sharedStylesHost.removeHost(this.shadowRoot)}},Xa=class extends Ja{sharedStylesHost;removeStylesOnCompDestroy;styles;styleUrls;constructor(t,e,o,i,r,a,s,c,p){super(t,r,a,s,c),this.sharedStylesHost=e,this.removeStylesOnCompDestroy=i;let f=o.styles,_=p?P0(p,f):f;this.styles=_.map(C=>C.replace(/%NS%/g,c)),this.styleUrls=o.getExternalStyles?.(p)}applyStyles(){this.sharedStylesHost.addStyles(this.styles,this.styleUrls)}destroy(){this.removeStylesOnCompDestroy&&wo.size===0&&this.sharedStylesHost.removeStyles(this.styles,this.styleUrls)}},Hl=class extends Xa{contentAttr;hostAttr;constructor(t,e,o,i,r,a,s,c,p){let f=i+"-"+o.id;super(t,e,o,r,a,s,c,p,f),this.contentAttr=ak(f),this.hostAttr=sk(f)}applyToHost(t){this.applyStyles(),this.setAttribute(t,this.hostAttr,"")}createElement(t,e){let o=super.createElement(t,e);return super.setAttribute(o,this.contentAttr,""),o}};var Gl=class n extends Ua{supportsDOMEvents=!0;static makeCurrent(){Zp(new n)}onAndCancel(t,e,o,i){return t.addEventListener(e,o,i),()=>{t.removeEventListener(e,o,i)}}dispatchEvent(t,e){t.dispatchEvent(e)}remove(t){t.remove()}createElement(t,e){return e=e||this.getDefaultDocument(),e.createElement(t)}createHtmlDocument(){return document.implementation.createHTMLDocument("fakeTitle")}getDefaultDocument(){return document}isElementNode(t){return t.nodeType===Node.ELEMENT_NODE}isShadowRoot(t){return t instanceof DocumentFragment}getGlobalEventTarget(t,e){return e==="window"?window:e==="document"?t:e==="body"?t.body:null}getBaseHref(t){let e=ck();return e==null?null:lk(e)}resetBaseElement(){es=null}getUserAgent(){return window.navigator.userAgent}getCookie(t){return Za(document.cookie,t)}},es=null;function ck(){return es=es||document.head.querySelector("base"),es?es.getAttribute("href"):null}function lk(n){return new URL(n,document.baseURI).pathname}var O0=["alt","control","meta","shift"],dk={"\b":"Backspace"," ":"Tab","\x7F":"Delete","\x1B":"Escape",Del:"Delete",Esc:"Escape",Left:"ArrowLeft",Right:"ArrowRight",Up:"ArrowUp",Down:"ArrowDown",Menu:"ContextMenu",Scroll:"ScrollLock",Win:"OS"},uk={alt:n=>n.altKey,control:n=>n.ctrlKey,meta:n=>n.metaKey,shift:n=>n.shiftKey},v0=(()=>{class n extends Qa{constructor(e){super(e)}supports(e){return n.parseEventName(e)!=null}addEventListener(e,o,i,r){let a=n.parseEventName(o),s=n.eventCallback(a.fullKey,i,this.manager.getZone());return this.manager.getZone().runOutsideAngular(()=>Kt().onAndCancel(e,a.domEventName,s,r))}static parseEventName(e){let o=e.toLowerCase().split("."),i=o.shift();if(o.length===0||!(i==="keydown"||i==="keyup"))return null;let r=n._normalizeKey(o.pop()),a="",s=o.indexOf("code");if(s>-1&&(o.splice(s,1),a="code."),O0.forEach(p=>{let f=o.indexOf(p);f>-1&&(o.splice(f,1),a+=p+".")}),a+=r,o.length!=0||r.length===0)return null;let c={};return c.domEventName=i,c.fullKey=a,c}static matchEventFullKeyCode(e,o){let i=dk[e.key]||e.key,r="";return o.indexOf("code.")>-1&&(i=e.code,r="code."),i==null||!i?!1:(i=i.toLowerCase(),i===" "?i="space":i==="."&&(i="dot"),O0.forEach(a=>{if(a!==i){let s=uk[a];s(e)&&(r+=a+".")}}),r+=i,r===o)}static eventCallback(e,o,i){return r=>{n.matchEventFullKeyCode(r,e)&&i.runGuarded(()=>o(r))}}static _normalizeKey(e){return e==="esc"?"escape":e}static \u0275fac=function(o){return new(o||n)(Q(Ue))};static \u0275prov=te({token:n,factory:n.\u0275fac})}return n})();function df(n,t,e){return be(this,null,function*(){let o=S({rootComponent:n},gk(t,e));return KM(o)})}function gk(n,t){return{platformRef:t?.platformRef,appProviders:[...hk,...n?.providers??[]],platformProviders:_k}}function pk(){Gl.makeCurrent()}function fk(){return new nn}function mk(){return gp(document),document}var _k=[{provide:Ca,useValue:_0},{provide:Fc,useValue:pk,multi:!0},{provide:Ue,useFactory:mk}];var hk=[{provide:ga,useValue:"root"},{provide:nn,useFactory:fk},{provide:Ul,useClass:zl,multi:!0},{provide:Ul,useClass:v0,multi:!0},lf,{provide:Oi,useClass:sf},{provide:sf,useExisting:Oi},af,{provide:Ci,useExisting:lf},[]];var Fn=class n{headers;normalizedNames=new Map;lazyInit;lazyUpdate=null;constructor(t){t?typeof t=="string"?this.lazyInit=()=>{this.headers=new Map,t.split(` +`).forEach(e=>{let o=e.indexOf(":");if(o>0){let i=e.slice(0,o),r=e.slice(o+1).trim();this.addHeaderEntry(i,r)}})}:typeof Headers<"u"&&t instanceof Headers?(this.headers=new Map,t.forEach((e,o)=>{this.addHeaderEntry(o,e)})):this.lazyInit=()=>{this.headers=new Map,Object.entries(t).forEach(([e,o])=>{this.setHeaderEntries(e,o)})}:this.headers=new Map}has(t){return this.init(),this.headers.has(t.toLowerCase())}get(t){this.init();let e=this.headers.get(t.toLowerCase());return e&&e.length>0?e[0]:null}keys(){return this.init(),Array.from(this.normalizedNames.values())}getAll(t){return this.init(),this.headers.get(t.toLowerCase())||null}append(t,e){return this.clone({name:t,value:e,op:"a"})}set(t,e){return this.clone({name:t,value:e,op:"s"})}delete(t,e){return this.clone({name:t,value:e,op:"d"})}maybeSetNormalizedName(t,e){this.normalizedNames.has(e)||this.normalizedNames.set(e,t)}init(){this.lazyInit&&(this.lazyInit instanceof n?this.copyFrom(this.lazyInit):this.lazyInit(),this.lazyInit=null,this.lazyUpdate&&(this.lazyUpdate.forEach(t=>this.applyUpdate(t)),this.lazyUpdate=null))}copyFrom(t){t.init();for(let[e,o]of t.headers.entries())this.headers.set(e,o),this.normalizedNames.set(e,t.normalizedNames.get(e))}clone(t){let e=new n;return e.lazyInit=this.lazyInit&&this.lazyInit instanceof n?this.lazyInit:this,e.lazyUpdate=(this.lazyUpdate||[]).concat([t]),e}applyUpdate(t){let e=t.name.toLowerCase();switch(t.op){case"a":case"s":let o=t.value;if(typeof o=="string"&&(o=[o]),o.length===0)return;this.maybeSetNormalizedName(t.name,e);let i=t.op==="a"?(this.headers.get(e)||[]).slice():[];i.push(...o),this.headers.set(e,i);break;case"d":let r=t.value;if(r===void 0)this.headers.delete(e),this.normalizedNames.delete(e);else{let a=Array.isArray(r)?r:[r],s=this.headers.get(e);if(!s)return;s=s.filter(c=>a.indexOf(c)===-1),s.length===0?(this.headers.delete(e),this.normalizedNames.delete(e)):this.headers.set(e,s)}break}}addHeaderEntry(t,e){let o=t.toLowerCase();this.maybeSetNormalizedName(t,o),this.headers.has(o)?this.headers.get(o).push(e):this.headers.set(o,[e])}setHeaderEntries(t,e){let o=(Array.isArray(e)?e:[e]).map(r=>r.toString()),i=t.toLowerCase();this.headers.set(i,o),this.maybeSetNormalizedName(t,i)}forEach(t){this.init(),Array.from(this.normalizedNames.keys()).forEach(e=>t(this.normalizedNames.get(e),this.headers.get(e)))}};var Wl=class{map=new Map;set(t,e){return this.map.set(t,e),this}get(t){return this.map.has(t)||this.map.set(t,t.defaultValue()),this.map.get(t)}delete(t){return this.map.delete(t),this}has(t){return this.map.has(t)}keys(){return this.map.keys()}},Kl=class{encodeKey(t){return y0(t)}encodeValue(t){return y0(t)}decodeKey(t){return decodeURIComponent(t)}decodeValue(t){return decodeURIComponent(t)}};function Ck(n,t){let e=new Map;return n.length>0&&n.replace(/^\?/,"").split("&").forEach(i=>{let r=i.indexOf("="),[a,s]=r==-1?[t.decodeKey(i),""]:[t.decodeKey(i.slice(0,r)),t.decodeValue(i.slice(r+1))],c=e.get(a)||[];c.push(s),e.set(a,c)}),e}var Mk=/%(\d[a-f0-9])/gi,bk={40:"@","3A":":",24:"$","2C":",","3B":";","3D":"=","3F":"?","2F":"/"};function y0(n){return encodeURIComponent(n).replace(Mk,(t,e)=>bk[e]??t)}function ql(n){return`${n}`}var ao=class n{map;encoder;updates=null;cloneFrom=null;constructor(t={}){if(this.encoder=t.encoder||new Kl,t.fromString){if(t.fromObject)throw new z(2805,!1);this.map=Ck(t.fromString,this.encoder)}else t.fromObject?(this.map=new Map,Object.keys(t.fromObject).forEach(e=>{let o=t.fromObject[e],i=Array.isArray(o)?o.map(ql):[ql(o)];this.map.set(e,i)})):this.map=null}has(t){return this.init(),this.map.has(t)}get(t){this.init();let e=this.map.get(t);return e?e[0]:null}getAll(t){return this.init(),this.map.get(t)||null}keys(){return this.init(),Array.from(this.map.keys())}append(t,e){return this.clone({param:t,value:e,op:"a"})}appendAll(t){let e=[];return Object.keys(t).forEach(o=>{let i=t[o];Array.isArray(i)?i.forEach(r=>{e.push({param:o,value:r,op:"a"})}):e.push({param:o,value:i,op:"a"})}),this.clone(e)}set(t,e){return this.clone({param:t,value:e,op:"s"})}delete(t,e){return this.clone({param:t,value:e,op:"d"})}toString(){return this.init(),this.keys().map(t=>{let e=this.encoder.encodeKey(t);return this.map.get(t).map(o=>e+"="+this.encoder.encodeValue(o)).join("&")}).filter(t=>t!=="").join("&")}clone(t){let e=new n({encoder:this.encoder});return e.cloneFrom=this.cloneFrom||this,e.updates=(this.updates||[]).concat(t),e}init(){if(this.map===null&&(this.map=new Map),this.cloneFrom!==null){this.cloneFrom.init();for(let[t,e]of this.cloneFrom.map.entries())this.map.set(t,e);this.updates.forEach(t=>{switch(t.op){case"a":case"s":let e=t.op==="a"?(this.map.get(t.param)||[]).slice():[];e.push(ql(t.value)),this.map.set(t.param,e);break;case"d":if(t.value!==void 0){let o=(this.map.get(t.param)||[]).slice(),i=o.indexOf(ql(t.value));i!==-1&&o.splice(i,1),o.length>0?this.map.set(t.param,o):this.map.delete(t.param)}else{this.map.delete(t.param);break}}}),this.cloneFrom=this.updates=null}}};function Pk(n){switch(n){case"DELETE":case"GET":case"HEAD":case"OPTIONS":case"JSONP":return!1;default:return!0}}function x0(n){return typeof ArrayBuffer<"u"&&n instanceof ArrayBuffer}function w0(n){return typeof Blob<"u"&&n instanceof Blob}function S0(n){return typeof FormData<"u"&&n instanceof FormData}function Ok(n){return typeof URLSearchParams<"u"&&n instanceof URLSearchParams}var ts="Content-Type",Yl="Accept",D0="text/plain",I0="application/json",T0=`${I0}, ${D0}, */*`,Pr=class n{url;body=null;headers;context;reportProgress=!1;reportUploadProgress=!1;reportDownloadProgress=!1;withCredentials=!1;credentials;keepalive=!1;cache;priority;mode;redirect;referrer;integrity;referrerPolicy;responseType="json";method;params;urlWithParams;transferCache;timeout;constructor(t,e,o,i){this.url=e,this.method=t.toUpperCase();let r;if(Pk(this.method)||i?(this.body=o!==void 0?o:null,r=i):r=o,r){if(this.reportProgress=!!r.reportProgress,this.reportUploadProgress=!!r.reportUploadProgress,this.reportDownloadProgress=!!r.reportDownloadProgress,this.withCredentials=!!r.withCredentials,this.keepalive=!!r.keepalive,r.responseType&&(this.responseType=r.responseType),r.headers&&(this.headers=r.headers),r.context&&(this.context=r.context),r.params&&(this.params=r.params),r.priority&&(this.priority=r.priority),r.cache&&(this.cache=r.cache),r.credentials&&(this.credentials=r.credentials),typeof r.timeout=="number"){if(r.timeout<1||!Number.isInteger(r.timeout))throw new z(2822,"");this.timeout=r.timeout}r.mode&&(this.mode=r.mode),r.redirect&&(this.redirect=r.redirect),r.integrity&&(this.integrity=r.integrity),r.referrer!==void 0&&(this.referrer=r.referrer),r.referrerPolicy&&(this.referrerPolicy=r.referrerPolicy),this.transferCache=r.transferCache}if(this.headers??=new Fn,this.context??=new Wl,!this.params)this.params=new ao,this.urlWithParams=e;else{let a=this.params.toString();if(a.length===0)this.urlWithParams=e;else{let s=e,c="",p=e.indexOf("#");p!==-1&&(c=e.substring(p),s=e.substring(0,p));let f=s.indexOf("?"),_=f===-1?"?":fMe.set(Ee,t.setHeaders[Ee]),J)),t.setParams&&(X=Object.keys(t.setParams).reduce((Me,Ee)=>Me.set(Ee,t.setParams[Ee]),X)),new n(e,o,A,{params:X,headers:J,context:W,reportProgress:E,reportUploadProgress:U,reportDownloadProgress:ne,responseType:i,withCredentials:N,transferCache:P,keepalive:r,cache:s,priority:a,timeout:T,mode:c,redirect:p,credentials:f,referrer:_,integrity:C,referrerPolicy:h})}},Rn=(function(n){return n[n.Sent=0]="Sent",n[n.UploadProgress=1]="UploadProgress",n[n.ResponseHeader=2]="ResponseHeader",n[n.DownloadProgress=3]="DownloadProgress",n[n.Response=4]="Response",n[n.User=5]="User",n})(Rn||{}),Or=class{headers;status;statusText;url;ok;type;redirected;responseType;constructor(t,e=200,o="OK"){this.headers=t.headers||new Fn,this.status=t.status!==void 0?t.status:e,this.statusText=t.statusText||o,this.url=t.url||null,this.redirected=t.redirected,this.responseType=t.responseType,this.ok=this.status>=200&&this.status<300}},ns=class n extends Or{constructor(t={}){super(t)}type=Rn.ResponseHeader;clone(t={}){return new n({headers:t.headers||this.headers,status:t.status!==void 0?t.status:this.status,statusText:t.statusText||this.statusText,url:t.url||this.url||void 0})}},vr=class n extends Or{body;constructor(t={}){super(t),this.body=t.body!==void 0?t.body:null}type=Rn.Response;clone(t={}){return new n({body:t.body!==void 0?t.body:this.body,headers:t.headers||this.headers,status:t.status!==void 0?t.status:this.status,statusText:t.statusText||this.statusText,url:t.url||this.url||void 0,redirected:t.redirected??this.redirected,responseType:t.responseType??this.responseType})}},Nn=class extends Or{name="HttpErrorResponse";message;error;ok=!1;constructor(t){super(t,0,"Unknown Error"),this.status>=200&&this.status<300?this.message=`Http failure during parsing for ${t.url||"(unknown url)"}`:this.message=`Http failure response for ${t.url||"(unknown url)"}: ${t.status} ${t.statusText}`,this.error=t.error||null}},A0=200,vk=204;var yk=/^\)\]\}',?\n/,Dq=1024*1024,N0=new B("",{factory:()=>null}),Zl=(()=>{class n{fetchImpl=D(gf,{optional:!0})?.fetch??((...e)=>globalThis.fetch(...e));ngZone=D(ge);destroyRef=D(Ae);maxResponseSize=D(N0);handle(e){return new ie(o=>{let i=new AbortController,r=!1,a={next:c=>{c.type===Rn.Response&&(r=!0),o.next(c)},error:c=>{r=!0,o.error(c)},complete:()=>{r=!0,o.complete()}};this.doRequest(e,i.signal,a).then(pf,c=>a.error(new Nn({error:c})));let s;return e.timeout&&(s=this.ngZone.runOutsideAngular(()=>setTimeout(()=>{i.signal.aborted||i.abort(new DOMException("signal timed out","TimeoutError"))},e.timeout))),()=>{s!==void 0&&clearTimeout(s),!r&&!i.signal.aborted&&i.abort()}})}doRequest(e,o,i){return be(this,null,function*(){let r=this.createRequestInit(e),a;try{let A=this.ngZone.runOutsideAngular(()=>this.fetchImpl(e.urlWithParams,S({signal:o},r)));xk(A),i.next({type:Rn.Sent}),a=yield A}catch(A){i.error(new Nn({error:A,status:A.status??0,statusText:A.statusText,url:e.urlWithParams,headers:A.headers}));return}let s=new Fn(a.headers),c=a.statusText,p=a.url||e.urlWithParams,f=a.status,_=null,C=e.reportProgress||e.reportDownloadProgress;if(C&&i.next(new ns({headers:s,status:f,statusText:c,url:p})),a.body){let A=a.headers.get(ts)??"",N=a.headers.get("content-length"),E=N!==null?Number(N):NaN;this.maxResponseSize!==null&&Number.isFinite(E)&&E>this.maxResponseSize&&(yield a.body.cancel(),k0(this.maxResponseSize));let U=[],ne=a.body.getReader(),J=0,X,W,Me=typeof Zone<"u"&&Zone.current,Ee=!1;if(yield this.ngZone.runOutsideAngular(()=>be(this,null,function*(){for(;;){if(this.destroyRef.destroyed){yield ne.cancel(),Ee=!0;break}let{done:Jt,value:Xt}=yield ne.read();if(Jt)break;if(U.push(Xt),J+=Xt.length,this.maxResponseSize!==null&&J>this.maxResponseSize&&(yield ne.cancel(),k0(this.maxResponseSize)),C){W=e.responseType==="text"?(W??"")+(X??=E0(A)).decode(Xt,{stream:!0}):void 0;let $t=()=>i.next({type:Rn.DownloadProgress,total:Number.isFinite(E)?E:void 0,loaded:J,partialText:W});Me?Me.run($t):$t()}}})),Ee){i.complete();return}let ft=this.concatChunks(U,J);try{_=this.parseBody(e,ft,A,f)}catch(Jt){i.error(new Nn({error:Jt,headers:new Fn(a.headers),status:a.status,statusText:a.statusText,url:a.url||e.urlWithParams}));return}}f===0&&(f=_?A0:0);let h=f>=200&&f<300,P=a.redirected,T=a.type;h?(i.next(new vr({body:_,headers:s,status:f,statusText:c,url:p,redirected:P,responseType:T})),i.complete()):i.error(new Nn({error:_,headers:s,status:f,statusText:c,url:p,redirected:P,responseType:T}))})}parseBody(e,o,i,r){switch(e.responseType){case"json":let a=new TextDecoder().decode(o).replace(yk,"");if(a==="")return null;try{return JSON.parse(a)}catch(s){if(r<200||r>=300)return a;throw s}case"text":return E0(i).decode(o);case"blob":return new Blob([o],{type:i});case"arraybuffer":return o.buffer}}createRequestInit(e){if(e.reportUploadProgress)throw new z(2824,!1);let o={},i;if(i=e.credentials,e.withCredentials&&(i="include"),e.headers.forEach((r,a)=>o[r]=a.join(",")),e.headers.has(Yl)||(o[Yl]=T0),!e.headers.has(ts)){let r=e.detectContentTypeHeader();r!==null&&(o[ts]=r)}return{body:e.serializeBody(),method:e.method,headers:o,credentials:i,keepalive:e.keepalive,cache:e.cache,priority:e.priority,mode:e.mode,redirect:e.redirect,referrer:e.referrer,integrity:e.integrity,referrerPolicy:e.referrerPolicy}}concatChunks(e,o){let i=new Uint8Array(o),r=0;for(let a of e)i.set(a,r),r+=a.length;return i}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),gf=class{};function pf(){}function xk(n){n.then(pf,pf)}function k0(n){throw new z(-2825,!1)}var wk=/charset=\s*["']?([^;"'\s]+)["']?/i;function E0(n){let t=n.match(wk);if(t!==null)try{return new TextDecoder(t[1])}catch(e){}return new TextDecoder}var Sk=new B("",{factory:()=>!0}),kk="XSRF-TOKEN",Ek=new B("",{factory:()=>kk}),Dk="X-XSRF-TOKEN",Ik=new B("",{factory:()=>Dk}),Tk=(()=>{class n{cookieName=D(Ek);doc=D(Ue);lastCookieString="";lastToken=null;parseCount=0;getToken(){let e=this.doc.cookie||"";return e!==this.lastCookieString&&(this.parseCount++,this.lastToken=Za(e,this.cookieName),this.lastCookieString=e),this.lastToken}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),R0=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:function(o){let i=null;return o?i=new(o||n):i=Q(Tk),i},providedIn:"root"})}return n})();function F0(n,t){if(!D(Sk)||n.method==="GET"||n.method==="HEAD")return t(n);try{let i=D(Cr).href,{origin:r}=new URL(i),{origin:a}=new URL(n.url,r);if(r!==a)return t(n)}catch(i){return t(n)}let e=D(R0).getToken(),o=D(Ik);return e!=null&&!n.headers.has(o)&&(n=n.clone({headers:n.headers.set(o,e)})),t(n)}function Ak(n,t){return t(n)}function Nk(n,t,e){return(o,i)=>et(e,()=>t(o,r=>n(r,i)))}var L0=new B("",{factory:()=>[F0]}),V0=new B(""),j0=new B("",{factory:()=>!0});var Jl=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:function(o){let i=null;return o?i=new(o||n):i=Q(Zl),i},providedIn:"root"})}return n})();var Ql=(()=>{class n{backend;injector;chain=null;pendingTasks=D(Oa);contributeToStability=D(j0);constructor(e,o){this.backend=e,this.injector=o}handle(e){if(this.chain===null){let i=this.injector.get(Xl,null,{skipSelf:!0}),r=i!==null&&this.backend===i,a=this.injector.get(V0,[],r?{self:!0}:void 0),s=Array.from(new Set([...this.injector.get(L0),...a]));this.chain=s.reduceRight((c,p)=>Nk(c,p,this.injector),Ak)}let o=this.chain;if(this.contributeToStability){let i=this.pendingTasks.add();return de(()=>o(e,r=>this.backend.handle(r))).pipe(_o(i))}else return de(()=>o(e,i=>this.backend.handle(i)))}static \u0275fac=function(o){return new(o||n)(Q(Jl),Q(Le))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})(),Xl=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=te({token:n,factory:function(o){let i=null;return o?i=new(o||n):i=Q(Ql),i},providedIn:"root"})}return n})();function uf(n,t){return S({body:t},n)}var Ro=(()=>{class n{handler;constructor(e){this.handler=e}request(e,o,i={}){let r;if(e instanceof Pr)r=e;else{let c;i.headers instanceof Fn?c=i.headers:c=new Fn(i.headers);let p;i.params&&(i.params instanceof ao?p=i.params:p=new ao({fromObject:i.params})),r=new Pr(e,o,i.body!==void 0?i.body:null,{headers:c,context:i.context,params:p,reportProgress:i.reportProgress,reportUploadProgress:i.reportUploadProgress,reportDownloadProgress:i.reportDownloadProgress,responseType:i.responseType||"json",withCredentials:i.withCredentials,transferCache:i.transferCache,keepalive:i.keepalive,priority:i.priority,cache:i.cache,mode:i.mode,redirect:i.redirect,credentials:i.credentials,referrer:i.referrer,referrerPolicy:i.referrerPolicy,integrity:i.integrity,timeout:i.timeout})}let a=se(r).pipe(Jo(c=>this.handler.handle(c)));if(e instanceof Pr||i.observe==="events")return a;let s=a.pipe(Bt(c=>c instanceof vr));switch(i.observe||"body"){case"body":switch(r.responseType){case"arraybuffer":return s.pipe(Pe(c=>{if(c.body!==null&&!(c.body instanceof ArrayBuffer))throw new z(2806,!1);return c.body}));case"blob":return s.pipe(Pe(c=>{if(c.body!==null&&!(c.body instanceof Blob))throw new z(2807,!1);return c.body}));case"text":return s.pipe(Pe(c=>{if(c.body!==null&&typeof c.body!="string")throw new z(2808,!1);return c.body}));default:return s.pipe(Pe(c=>c.body))}case"response":return s;default:throw new z(2809,!1)}}delete(e,o={}){return this.request("DELETE",e,o)}get(e,o={}){return this.request("GET",e,o)}head(e,o={}){return this.request("HEAD",e,o)}jsonp(e,o){return this.request("JSONP",e,{params:new ao().append(o,"JSONP_CALLBACK"),observe:"body",responseType:"json"})}options(e,o={}){return this.request("OPTIONS",e,o)}patch(e,o,i={}){return this.request("PATCH",e,uf(i,o))}post(e,o,i={}){return this.request("POST",e,uf(i,o))}put(e,o,i={}){return this.request("PUT",e,uf(i,o))}static \u0275fac=function(o){return new(o||n)(Q(Xl))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})();var Rk=/^\)\]\}',?\n/;var ff=(()=>{class n{xhrFactory;tracingService=D(Tn,{optional:!0});constructor(e){this.xhrFactory=e}maybePropagateTrace(e){return this.tracingService?.propagate?this.tracingService.propagate(e):e}handle(e){if(e.method==="JSONP")throw new z(-2800,!1);let o=this.xhrFactory;return se(null).pipe(vt(()=>new ie(r=>{let a=o.build();if(a.open(e.method,e.urlWithParams),e.withCredentials&&(a.withCredentials=!0),e.headers.forEach((E,U)=>a.setRequestHeader(E,U.join(","))),e.headers.has(Yl)||a.setRequestHeader(Yl,T0),!e.headers.has(ts)){let E=e.detectContentTypeHeader();E!==null&&a.setRequestHeader(ts,E)}if(e.timeout&&(a.timeout=e.timeout),e.responseType){let E=e.responseType.toLowerCase();a.responseType=E!=="json"?E:"text"}let s=e.serializeBody(),c=null,p=()=>{if(c!==null)return c;let E=a.statusText||"OK",U=new Fn(a.getAllResponseHeaders()),ne=a.responseURL||e.url;return c=new ns({headers:U,status:a.status,statusText:E,url:ne}),c},f=this.maybePropagateTrace(()=>{let{headers:E,status:U,statusText:ne,url:J}=p(),X=null;U!==vk&&(X=typeof a.response>"u"?a.responseText:a.response),U===0&&(U=X?A0:0);let W=U>=200&&U<300;if(e.responseType==="json"&&typeof X=="string"){let Me=X;X=X.replace(Rk,"");try{X=X!==""?JSON.parse(X):null}catch(Ee){X=Me,W&&(W=!1,X={error:Ee,text:X})}}W?(r.next(new vr({body:X,headers:E,status:U,statusText:ne,url:J||void 0})),r.complete()):r.error(new Nn({error:X,headers:E,status:U,statusText:ne,url:J||void 0}))}),_=this.maybePropagateTrace(E=>{let{url:U}=p(),ne=new Nn({error:E,status:a.status||0,statusText:a.statusText||"Unknown Error",url:U||void 0});r.error(ne)}),C=_;e.timeout&&(C=this.maybePropagateTrace(E=>{let{url:U}=p(),ne=new Nn({error:new DOMException("Request timed out","TimeoutError"),status:a.status||0,statusText:a.statusText||"Request timeout",url:U||void 0});r.error(ne)}));let h=!1,P=this.maybePropagateTrace(E=>{h||(r.next(p()),h=!0);let U={type:Rn.DownloadProgress,loaded:E.loaded};E.lengthComputable&&(U.total=E.total),e.responseType==="text"&&a.responseText&&(U.partialText=a.responseText),r.next(U)}),T=this.maybePropagateTrace(E=>{let U={type:Rn.UploadProgress,loaded:E.loaded};E.lengthComputable&&(U.total=E.total),r.next(U)});a.addEventListener("load",f),a.addEventListener("error",_),a.addEventListener("timeout",C),a.addEventListener("abort",_);let A=e.reportProgress||e.reportUploadProgress,N=e.reportProgress||e.reportDownloadProgress;return N&&a.addEventListener("progress",P),A&&s!==null&&a.upload&&a.upload.addEventListener("progress",T),a.send(s),r.next({type:Rn.Sent}),()=>{a.removeEventListener("error",_),a.removeEventListener("abort",_),a.removeEventListener("load",f),a.removeEventListener("timeout",C),N&&a.removeEventListener("progress",P),A&&s!==null&&a.upload&&a.upload.removeEventListener("progress",T),a.readyState!==a.DONE&&a.abort()}})))}static \u0275fac=function(o){return new(o||n)(Q(tf))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})(),mf=(function(n){return n[n.Interceptors=0]="Interceptors",n[n.LegacyInterceptors=1]="LegacyInterceptors",n[n.CustomXsrfConfiguration=2]="CustomXsrfConfiguration",n[n.NoXsrfProtection=3]="NoXsrfProtection",n[n.JsonpSupport=4]="JsonpSupport",n[n.RequestsMadeViaParent=5]="RequestsMadeViaParent",n[n.Fetch=6]="Fetch",n[n.Xhr=7]="Xhr",n})(mf||{});function Fk(n,t){return{\u0275kind:n,\u0275providers:t}}function _f(...n){let t=[Ro,Zl,Ql,{provide:Xl,useExisting:Ql},{provide:Jl,useFactory:()=>D(Zl)},{provide:L0,useValue:F0,multi:!0}];for(let e of n)t.push(...e.\u0275providers);return bo(t)}function hf(){return Fk(mf.Xhr,[ff,{provide:Jl,useExisting:ff}])}var $0=(()=>{class n{_doc;constructor(e){this._doc=e}getTitle(){return this._doc.title}setTitle(e){this._doc.title=e||""}static \u0275fac=function(o){return new(o||n)(Q(Ue))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})();var re="primary",_s=Symbol("RouteTitle"),Of=class{params;constructor(t){this.params=t||{}}has(t){return Object.hasOwn(this.params,t)}get(t){if(this.has(t)){let e=this.params[t];return Array.isArray(e)?e[0]:e}return null}getAll(t){if(this.has(t)){let e=this.params[t];return Array.isArray(e)?e:[e]}return[]}get keys(){return Object.keys(this.params)}};function Ii(n){return new Of(n)}function Cf(n,t,e){for(let o=0;on.length||e.pathMatch==="full"&&(t.hasChildren()||o.lengthn.length||e.pathMatch==="full"&&t.hasChildren()&&e.path!=="**")return null;let s={};return!Cf(r,n.slice(0,r.length),s)||!Cf(a,n.slice(n.length-a.length),s)?null:{consumed:n,posParams:s}}function rd(n){return new Promise((t,e)=>{n.pipe(Hn()).subscribe({next:o=>t(o),error:o=>e(o)})})}function jk(n,t){if(n.length!==t.length)return!1;for(let e=0;eo[r]===i)}else return n===t}function $k(n){return n.length>0?n[n.length-1]:null}function Ni(n){return nc(n)?n:Eo(n)?Fe(Promise.resolve(n)):se(n)}function Y0(n){return nc(n)?rd(n):Promise.resolve(n)}var zk={exact:Q0,subset:J0},Z0={exact:Bk,subset:Hk,ignored:()=>!0},Vf={paths:"exact",fragment:"ignored",matrixParams:"ignored",queryParams:"exact"},Sr={paths:"subset",fragment:"ignored",matrixParams:"ignored",queryParams:"subset"};function jf(n,t,e){let o=n instanceof bt?n:t.parseUrl(n);return I(()=>yf(t.lastSuccessfulNavigation()?.finalUrl??new bt,o,S(S({},Sr),e)))}function yf(n,t,e){return zk[e.paths](n.root,t.root,e.matrixParams)&&Z0[e.queryParams](n.queryParams,t.queryParams)&&!(e.fragment==="exact"&&n.fragment!==t.fragment)}function Bk(n,t){return Ln(n,t)}function Q0(n,t,e){if(!Di(n.segments,t.segments)||!nd(n.segments,t.segments,e)||n.numberOfChildren!==t.numberOfChildren)return!1;for(let o in t.children)if(!n.children[o]||!Q0(n.children[o],t.children[o],e))return!1;return!0}function Hk(n,t){return Object.keys(t).length<=Object.keys(n).length&&Object.keys(t).every(e=>K0(n[e],t[e]))}function J0(n,t,e){return X0(n,t,t.segments,e)}function X0(n,t,e,o){if(n.segments.length>e.length){let i=n.segments.slice(0,e.length);return!(!Di(i,e)||t.hasChildren()||!nd(i,e,o))}else if(n.segments.length===e.length){if(!Di(n.segments,e)||!nd(n.segments,e,o))return!1;for(let i in t.children)if(!n.children[i]||!J0(n.children[i],t.children[i],o))return!1;return!0}else{let i=e.slice(0,n.segments.length),r=e.slice(n.segments.length);return!Di(n.segments,i)||!nd(n.segments,i,o)||!n.children[re]?!1:X0(n.children[re],t,r,o)}}function nd(n,t,e){return t.every((o,i)=>Z0[e](n[i].parameters,o.parameters))}var bt=class{root;queryParams;fragment;_queryParamMap;constructor(t=new me([],{}),e={},o=null){this.root=t,this.queryParams=e,this.fragment=o}get queryParamMap(){return this._queryParamMap??=Ii(this.queryParams),this._queryParamMap}toString(){return qk.serialize(this)}},me=class{segments;children;parent=null;constructor(t,e){this.segments=t,this.children=e,Object.values(e).forEach(o=>o.parent=this)}hasChildren(){return this.numberOfChildren>0}get numberOfChildren(){return Object.keys(this.children).length}toString(){return od(this)}},Fo=class{path;parameters;_parameterMap;constructor(t,e){this.path=t,this.parameters=e}get parameterMap(){return this._parameterMap??=Ii(this.parameters),this._parameterMap}toString(){return tb(this)}};function Uk(n,t){return Di(n,t)&&n.every((e,o)=>Ln(e.parameters,t[o].parameters))}function Di(n,t){return n.length!==t.length?!1:n.every((e,o)=>e.path===t[o].path)}function Gk(n,t){let e=[];return Object.entries(n.children).forEach(([o,i])=>{o===re&&(e=e.concat(t(i,o)))}),Object.entries(n.children).forEach(([o,i])=>{o!==re&&(e=e.concat(t(i,o)))}),e}var Ar=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:()=>new Lo})}return n})(),Lo=class{parse(t){let e=new wf(t);return new bt(e.parseRootSegment(),e.parseQueryParams(),e.parseFragment())}serialize(t){let e=`/${os(t.root,!0)}`,o=Yk(t.queryParams),i=typeof t.fragment=="string"?`#${Wk(t.fragment)}`:"";return`${e}${o}${i}`}},qk=new Lo;function od(n){return n.segments.map(t=>tb(t)).join("/")}function os(n,t){if(!n.hasChildren())return od(n);if(t){let e=n.children[re]?os(n.children[re],!1):"",o=[];return Object.entries(n.children).forEach(([i,r])=>{i!==re&&o.push(`${i}:${os(r,!1)}`)}),o.length>0?`${e}(${o.join("//")})`:e}else{let e=Gk(n,(o,i)=>i===re?[os(n.children[re],!1)]:[`${i}:${os(o,!1)}`]);return Object.keys(n.children).length===1&&n.children[re]!=null?`${od(n)}/${e[0]}`:`${od(n)}/(${e.join("//")})`}}function eb(n){return encodeURIComponent(n).replace(/%40/g,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",")}function ed(n){return eb(n).replace(/%3B/gi,";")}function Wk(n){return encodeURI(n)}function xf(n){return eb(n).replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/%26/gi,"&")}function id(n){return decodeURIComponent(n)}function z0(n){return id(n.replace(/\+/g,"%20"))}function tb(n){return`${xf(n.path)}${Kk(n.parameters)}`}function Kk(n){return Object.entries(n).map(([t,e])=>`;${xf(t)}=${xf(e)}`).join("")}function Yk(n){let t=Object.entries(n).map(([e,o])=>Array.isArray(o)?o.map(i=>`${ed(e)}=${ed(i)}`).join("&"):`${ed(e)}=${ed(o)}`).filter(e=>e);return t.length?`?${t.join("&")}`:""}var Zk=/^[^\/()?;#]+/;function Mf(n){let t=n.match(Zk);return t?t[0]:""}var Qk=/^[^\/()?;=#]+/;function Jk(n){let t=n.match(Qk);return t?t[0]:""}var Xk=/^[^=?&#]+/;function e2(n){let t=n.match(Xk);return t?t[0]:""}var t2=/^[^&#]+/;function n2(n){let t=n.match(t2);return t?t[0]:""}var wf=class{url;remaining;constructor(t){this.url=t,this.remaining=t}parseRootSegment(){for(;this.consumeOptional("/"););return this.remaining===""||this.peekStartsWith("?")||this.peekStartsWith("#")?new me([],{}):new me([],this.parseChildren())}parseQueryParams(){let t={};if(this.consumeOptional("?"))do this.parseQueryParam(t);while(this.consumeOptional("&"));return t}parseFragment(){return this.consumeOptional("#")?decodeURIComponent(this.remaining):null}parseChildren(t=0){if(t>50)throw new z(4010,!1);if(this.remaining==="")return{};this.consumeOptional("/");let e=[];for(this.peekStartsWith("(")||e.push(this.parseSegment());this.peekStartsWith("/")&&!this.peekStartsWith("//")&&!this.peekStartsWith("/(");)this.capture("/"),e.push(this.parseSegment());let o={};this.peekStartsWith("/(")&&(this.capture("/"),o=this.parseParens(!0,t));let i={};return this.peekStartsWith("(")&&(i=this.parseParens(!1,t)),(e.length>0||Object.keys(o).length>0)&&(i[re]=new me(e,o)),i}parseSegment(){let t=Mf(this.remaining);if(t===""&&this.peekStartsWith(";"))throw new z(4009,!1);return this.capture(t),new Fo(id(t),this.parseMatrixParams())}parseMatrixParams(){let t={};for(;this.consumeOptional(";");)this.parseParam(t);return t}parseParam(t){let e=Jk(this.remaining);if(!e)return;this.capture(e);let o="";if(this.consumeOptional("=")){let i=Mf(this.remaining);i&&(o=i,this.capture(o))}t[id(e)]=id(o)}parseQueryParam(t){let e=e2(this.remaining);if(!e)return;this.capture(e);let o="";if(this.consumeOptional("=")){let a=n2(this.remaining);a&&(o=a,this.capture(o))}let i=z0(e),r=z0(o);if(Object.hasOwn(t,i)){let a=t[i];Array.isArray(a)||(a=[a],t[i]=a),a.push(r)}else t[i]=r}parseParens(t,e){let o=Object.create(null);for(this.capture("(");!this.consumeOptional(")")&&this.remaining.length>0;){let i=Mf(this.remaining),r=this.remaining[i.length];if(r!=="/"&&r!==")"&&r!==";")throw new z(4010,!1);let a;i.indexOf(":")>-1?(a=i.slice(0,i.indexOf(":")),this.capture(a),this.capture(":")):t&&(a=re);let s=this.parseChildren(e+1);o[a??re]=Object.keys(s).length===1&&s[re]?s[re]:new me([],s),this.consumeOptional("//")}return o}peekStartsWith(t){return this.remaining.startsWith(t)}consumeOptional(t){return this.peekStartsWith(t)?(this.remaining=this.remaining.substring(t.length),!0):!1}capture(t){if(!this.consumeOptional(t))throw new z(4011,!1)}};function nb(n){return n.segments.length>0?new me([],{[re]:n}):n}function ob(n){let t=Object.create(null);for(let[o,i]of Object.entries(n.children)){let r=ob(i);if(o===re&&r.segments.length===0&&r.hasChildren())for(let[a,s]of Object.entries(r.children))t[a]=s;else(r.segments.length>0||r.hasChildren())&&(t[o]=r)}let e=new me(n.segments,t);return o2(e)}function o2(n){if(n.numberOfChildren===1&&n.children[re]){let t=n.children[re];return new me(n.segments.concat(t.segments),t.children)}return n}function Vo(n){return n instanceof bt}function ib(n,t,e=null,o=null,i=new Lo){let r=rb(n);return ab(r,t,e,o,i)}function rb(n){let t;function e(r){let a={};for(let c of r.children){let p=e(c);a[c.outlet]=p}let s=new me(r.url,a);return r===n&&(t=s),s}let o=e(n.root),i=nb(o);return t??i}function ab(n,t,e,o,i){let r=n;for(;r.parent;)r=r.parent;if(t.length===0)return bf(r,r,r,e,o,i);let a=i2(t);if(a.toRoot())return bf(r,r,new me([],{}),e,o,i);let s=r2(a,r,n),c=s.processChildren?rs(s.segmentGroup,s.index,a.commands):cb(s.segmentGroup,s.index,a.commands);return bf(r,s.segmentGroup,c,e,o,i)}function ad(n){return typeof n=="object"&&n!=null&&!n.outlets&&!n.segmentPath}function cs(n){return typeof n=="object"&&n!=null&&n.outlets}function B0(n,t,e){n||="\u0275";let o=new bt;return o.queryParams={[n]:t},e.parse(e.serialize(o)).queryParams[n]}function bf(n,t,e,o,i,r){let a={};for(let[p,f]of Object.entries(o??{}))a[p]=Array.isArray(f)?f.map(_=>B0(p,_,r)):B0(p,f,r);let s;n===t?s=e:s=sb(n,t,e);let c=nb(ob(s));return new bt(c,a,i)}function sb(n,t,e){let o=Object.create(null);return Object.entries(n.children).forEach(([i,r])=>{r===t?o[i]=e:o[i]=sb(r,t,e)}),new me(n.segments,o)}var sd=class{isAbsolute;numberOfDoubleDots;commands;constructor(t,e,o){if(this.isAbsolute=t,this.numberOfDoubleDots=e,this.commands=o,t&&o.length>0&&ad(o[0]))throw new z(4003,!1);let i=o.find(cs);if(i&&i!==$k(o))throw new z(4004,!1)}toRoot(){return this.isAbsolute&&this.commands.length===1&&this.commands[0]=="/"}};function i2(n){if(typeof n[0]=="string"&&n.length===1&&n[0]==="/")return new sd(!0,0,n);let t=0,e=!1,o=n.reduce((i,r,a)=>{if(typeof r=="object"&&r!=null){if(r.outlets){let s={};return Object.entries(r.outlets).forEach(([c,p])=>{s[c]=typeof p=="string"?p.split("/"):p}),[...i,{outlets:s}]}if(r.segmentPath)return[...i,r.segmentPath]}return typeof r!="string"?[...i,r]:a===0?(r.split("/").forEach((s,c)=>{c==0&&s==="."||(c==0&&s===""?e=!0:s===".."?t++:s!=""&&i.push(s))}),i):[...i,r]},[]);return new sd(e,t,o)}var xr=class{segmentGroup;processChildren;index;constructor(t,e,o){this.segmentGroup=t,this.processChildren=e,this.index=o}};function r2(n,t,e){if(n.isAbsolute)return new xr(t,!0,0);if(!e)return new xr(t,!1,NaN);if(e.parent===null)return new xr(e,!0,0);let o=ad(n.commands[0])?0:1,i=e.segments.length-1+o;return a2(e,i,n.numberOfDoubleDots)}function a2(n,t,e){let o=n,i=t,r=e;for(;r>i;){if(r-=i,o=o.parent,!o)throw new z(4005,!1);i=o.segments.length}return new xr(o,!1,i-r)}function s2(n){return cs(n[0])?n[0].outlets:{[re]:n}}function cb(n,t,e){if(n??=new me([],{}),n.segments.length===0&&n.hasChildren())return rs(n,t,e);let o=c2(n,t,e),i=e.slice(o.commandIndex);if(o.match&&o.pathIndexr!==re)&&n.children[re]&&n.numberOfChildren===1&&n.children[re].segments.length===0){let r=rs(n.children[re],t,e);return new me(n.segments,r.children)}return Object.entries(o).forEach(([r,a])=>{typeof a=="string"&&(a=[a]),a!==null&&(i[r]=cb(n.children[r],t,a))}),Object.entries(n.children).forEach(([r,a])=>{o[r]===void 0&&(i[r]=a)}),new me(n.segments,i)}}function c2(n,t,e){let o=0,i=t,r={match:!1,pathIndex:0,commandIndex:0};for(;i=e.length)return r;let a=n.segments[i],s=e[o];if(cs(s))break;let c=`${s}`,p=o0&&c===void 0)break;if(c&&p&&typeof p=="object"&&p.outlets===void 0){if(!U0(c,p,a))return r;o+=2}else{if(!U0(c,{},a))return r;o++}i++}return{match:!0,pathIndex:i,commandIndex:o}}function Sf(n,t,e){let o=n.segments.slice(0,t),i=0;for(;i{typeof o=="string"&&(o=[o]),o!==null&&(t[e]=Sf(new me([],{}),0,o))}),t}function H0(n){let t={};return Object.entries(n).forEach(([e,o])=>t[e]=`${o}`),t}function U0(n,t,e){return n==e.path&&Ln(t,e.parameters)}var as="imperative",rt=(function(n){return n[n.NavigationStart=0]="NavigationStart",n[n.NavigationEnd=1]="NavigationEnd",n[n.NavigationCancel=2]="NavigationCancel",n[n.NavigationError=3]="NavigationError",n[n.RoutesRecognized=4]="RoutesRecognized",n[n.ResolveStart=5]="ResolveStart",n[n.ResolveEnd=6]="ResolveEnd",n[n.GuardsCheckStart=7]="GuardsCheckStart",n[n.GuardsCheckEnd=8]="GuardsCheckEnd",n[n.RouteConfigLoadStart=9]="RouteConfigLoadStart",n[n.RouteConfigLoadEnd=10]="RouteConfigLoadEnd",n[n.ChildActivationStart=11]="ChildActivationStart",n[n.ChildActivationEnd=12]="ChildActivationEnd",n[n.ActivationStart=13]="ActivationStart",n[n.ActivationEnd=14]="ActivationEnd",n[n.Scroll=15]="Scroll",n[n.NavigationSkipped=16]="NavigationSkipped",n})(rt||{}),jt=class{id;url;constructor(t,e){this.id=t,this.url=e}},Ti=class extends jt{type=rt.NavigationStart;navigationTrigger;restoredState;constructor(t,e,o="imperative",i=null){super(t,e),this.navigationTrigger=o,this.restoredState=i}toString(){return`NavigationStart(id: ${this.id}, url: '${this.url}')`}},mn=class extends jt{urlAfterRedirects;type=rt.NavigationEnd;constructor(t,e,o){super(t,e),this.urlAfterRedirects=o}toString(){return`NavigationEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}')`}},pt=(function(n){return n[n.Redirect=0]="Redirect",n[n.SupersededByNewNavigation=1]="SupersededByNewNavigation",n[n.NoDataFromResolver=2]="NoDataFromResolver",n[n.GuardRejected=3]="GuardRejected",n[n.Aborted=4]="Aborted",n})(pt||{}),ls=(function(n){return n[n.IgnoredSameUrlNavigation=0]="IgnoredSameUrlNavigation",n[n.IgnoredByUrlHandlingStrategy=1]="IgnoredByUrlHandlingStrategy",n})(ls||{}),Zt=class extends jt{reason;code;type=rt.NavigationCancel;constructor(t,e,o,i){super(t,e),this.reason=o,this.code=i}toString(){return`NavigationCancel(id: ${this.id}, url: '${this.url}')`}};function lb(n){return n instanceof Zt&&(n.code===pt.Redirect||n.code===pt.SupersededByNewNavigation)}var co=class extends jt{reason;code;type=rt.NavigationSkipped;constructor(t,e,o,i){super(t,e),this.reason=o,this.code=i}},Ai=class extends jt{error;target;type=rt.NavigationError;constructor(t,e,o,i){super(t,e),this.error=o,this.target=i}toString(){return`NavigationError(id: ${this.id}, url: '${this.url}', error: ${this.error})`}},ds=class extends jt{urlAfterRedirects;state;type=rt.RoutesRecognized;constructor(t,e,o,i){super(t,e),this.urlAfterRedirects=o,this.state=i}toString(){return`RoutesRecognized(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}},cd=class extends jt{urlAfterRedirects;state;type=rt.GuardsCheckStart;constructor(t,e,o,i){super(t,e),this.urlAfterRedirects=o,this.state=i}toString(){return`GuardsCheckStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}},ld=class extends jt{urlAfterRedirects;state;shouldActivate;type=rt.GuardsCheckEnd;constructor(t,e,o,i,r){super(t,e),this.urlAfterRedirects=o,this.state=i,this.shouldActivate=r}toString(){return`GuardsCheckEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state}, shouldActivate: ${this.shouldActivate})`}},dd=class extends jt{urlAfterRedirects;state;type=rt.ResolveStart;constructor(t,e,o,i){super(t,e),this.urlAfterRedirects=o,this.state=i}toString(){return`ResolveStart(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}},ud=class extends jt{urlAfterRedirects;state;type=rt.ResolveEnd;constructor(t,e,o,i){super(t,e),this.urlAfterRedirects=o,this.state=i}toString(){return`ResolveEnd(id: ${this.id}, url: '${this.url}', urlAfterRedirects: '${this.urlAfterRedirects}', state: ${this.state})`}},gd=class{route;type=rt.RouteConfigLoadStart;constructor(t){this.route=t}toString(){return`RouteConfigLoadStart(path: ${this.route.path})`}},pd=class{route;type=rt.RouteConfigLoadEnd;constructor(t){this.route=t}toString(){return`RouteConfigLoadEnd(path: ${this.route.path})`}},fd=class{snapshot;type=rt.ChildActivationStart;constructor(t){this.snapshot=t}toString(){return`ChildActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}},md=class{snapshot;type=rt.ChildActivationEnd;constructor(t){this.snapshot=t}toString(){return`ChildActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}},_d=class{snapshot;type=rt.ActivationStart;constructor(t){this.snapshot=t}toString(){return`ActivationStart(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}},hd=class{snapshot;type=rt.ActivationEnd;constructor(t){this.snapshot=t}toString(){return`ActivationEnd(path: '${this.snapshot.routeConfig&&this.snapshot.routeConfig.path||""}')`}};var kr=class{},us=class{},Er=class{url;navigationBehaviorOptions;constructor(t,e){this.url=t,this.navigationBehaviorOptions=e}};function d2(n){return!(n instanceof kr)&&!(n instanceof Er)&&!(n instanceof us)}var Cd=class{rootInjector;outlet=null;route=null;children;attachRef=null;get injector(){return this.route?.snapshot._environmentInjector??this.rootInjector}constructor(t){this.rootInjector=t,this.children=new Nr(this.rootInjector)}},Nr=(()=>{class n{rootInjector;contexts=new Map;constructor(e){this.rootInjector=e}onChildOutletCreated(e,o){let i=this.getOrCreateContext(e);i.outlet=o,this.contexts.set(e,i)}onChildOutletDestroyed(e){let o=this.getContext(e);o&&(o.outlet=null,o.attachRef=null)}onOutletDeactivated(){let e=this.contexts;return this.contexts=new Map,e}onOutletReAttached(e){this.contexts=e}getOrCreateContext(e){let o=this.getContext(e);return o||(o=new Cd(this.rootInjector),this.contexts.set(e,o)),o}getContext(e){return this.contexts.get(e)||null}static \u0275fac=function(o){return new(o||n)(Q(Le))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})(),Md=class{_root;constructor(t){this._root=t}get root(){return this._root.value}parent(t){let e=this.pathFromRoot(t);return e.length>1?e[e.length-2]:null}children(t){let e=kf(t,this._root);return e?e.children.map(o=>o.value):[]}firstChild(t){let e=kf(t,this._root);return e&&e.children.length>0?e.children[0].value:null}siblings(t){let e=Ef(t,this._root);return e.length<2?[]:e[e.length-2].children.map(i=>i.value).filter(i=>i!==t)}pathFromRoot(t){return Ef(t,this._root).map(e=>e.value)}};function kf(n,t){if(n===t.value)return t;for(let e of t.children){let o=kf(n,e);if(o)return o}return null}function Ef(n,t){if(n===t.value)return[t];for(let e of t.children){let o=Ef(n,e);if(o.length)return o.unshift(t),o}return[]}var Vt=class{value;children;constructor(t,e){this.value=t,this.children=e}toString(){return`TreeNode(${this.value})`}};function yr(n){let t={};return n&&n.children.forEach(e=>t[e.value.outlet]=e),t}var gs=class extends Md{snapshot;constructor(t,e){super(t),this.snapshot=e,zf(this,t)}toString(){return this.snapshot.toString()}};function db(n,t){let e=u2(n,t),o=new st([new Fo("",{})]),i=new st({}),r=new st({}),a=new st({}),s=new st(""),c=new lo(o,i,a,s,r,re,n,e.root);return c.snapshot=e.root,new gs(new Vt(c,[]),e)}function u2(n,t){let e={},o={},i={},a=new Dr([],e,i,"",o,re,n,null,{},t);return new ps("",new Vt(a,[]))}var lo=class{urlSubject;paramsSubject;queryParamsSubject;fragmentSubject;dataSubject;outlet;component;snapshot;_futureSnapshot;_routerState;_paramMap;_queryParamMap;title;url;params;queryParams;fragment;data;resources;_localInjector;pending;paramsSignal;queryParamsSignal;paramMapSignal;queryParamMapSignal;fragmentSignal;dataSignal;constructor(t,e,o,i,r,a,s,c){this.urlSubject=t,this.paramsSubject=e,this.queryParamsSubject=o,this.fragmentSubject=i,this.dataSubject=r,this.outlet=a,this.component=s,this._futureSnapshot=c,this.title=this.dataSubject?.pipe(Pe(p=>p[_s]))??se(void 0),this.url=t,this.params=e,this.queryParams=o,this.fragment=i,this.data=r}get routeConfig(){return this._futureSnapshot.routeConfig}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap??=this.params.pipe(Pe(t=>Ii(t))),this._paramMap}get queryParamMap(){return this._queryParamMap??=this.queryParams.pipe(Pe(t=>Ii(t))),this._queryParamMap}toString(){return this.snapshot?this.snapshot.toString():`Future(${this._futureSnapshot})`}_setPending(t){this._futureSnapshot=t,this.pending?.set(!0)}},g2="always";function $f(n,t,e){let o,{routeConfig:i}=n;return t!==null&&(e==="always"||i?.path===""||!t.component&&!t.routeConfig?.loadComponent)?o={params:S(S({},t.params),n.params),data:S(S({},t.data),n.data),resolve:S(S(S(S({},n.data),t.data),i?.data),n._resolvedData)}:o={params:S({},n.params),data:S({},n.data),resolve:S(S({},n.data),n._resolvedData??{})},i&&gb(i)&&(o.resolve[_s]=i.title),o}var Dr=class{url;params;queryParams;fragment;data;outlet;component;routeConfig;_resolve;_resolvedData;_routerState;_paramMap;_queryParamMap;_environmentInjector;resources;get title(){return this.data?.[_s]}constructor(t,e,o,i,r,a,s,c,p,f){this.url=t,this.params=e,this.queryParams=o,this.fragment=i,this.data=r,this.outlet=a,this.component=s,this.routeConfig=c,this._resolve=p,this._environmentInjector=f}get root(){return this._routerState.root}get parent(){return this._routerState.parent(this)}get firstChild(){return this._routerState.firstChild(this)}get children(){return this._routerState.children(this)}get pathFromRoot(){return this._routerState.pathFromRoot(this)}get paramMap(){return this._paramMap??=Ii(this.params),this._paramMap}get queryParamMap(){return this._queryParamMap??=Ii(this.queryParams),this._queryParamMap}toString(){let t=this.url.map(o=>o.toString()).join("/"),e=this.routeConfig?this.routeConfig.path:"";return`Route(url:'${t}', path:'${e}')`}},ps=class extends Md{url;constructor(t,e){super(e),this.url=t,zf(this,e)}toString(){return ub(this._root)}};function zf(n,t){t.value._routerState=n,t.children.forEach(e=>zf(n,e))}function ub(n){let t=n.children.length>0?` { ${n.children.map(ub).join(", ")} } `:"";return`${n.value}${t}`}function Pf(n){if(n.snapshot){let t=n.snapshot,e=n._futureSnapshot;n.snapshot=e,Ln(t.queryParams,e.queryParams)||n.queryParamsSubject.next(e.queryParams),t.fragment!==e.fragment&&n.fragmentSubject.next(e.fragment),Ln(t.params,e.params)||n.paramsSubject.next(e.params),jk(t.url,e.url)||n.urlSubject.next(e.url),Ln(t.data,e.data)||n.dataSubject.next(e.data)}else n.snapshot=n._futureSnapshot,n.dataSubject.next(n._futureSnapshot.data)}function Df(n,t){let e=Ln(n.params,t.params)&&Uk(n.url,t.url),o=!n.parent!=!t.parent;return e&&!o&&(!n.parent||Df(n.parent,t.parent))}function gb(n){return typeof n.title=="string"||n.title===null}var pb=new B(""),hs=(()=>{class n{activated=null;get activatedComponentRef(){return this.activated}_activatedRoute=null;name=re;activateEvents=new Ke;deactivateEvents=new Ke;attachEvents=new Ke;detachEvents=new Ke;routerOutletData=Al();parentContexts=D(Nr);location=D(vi);changeDetector=D(oo);inputBinder=D(vd,{optional:!0});supportsBindingToComponentInputs=!0;ngOnChanges(e){if(e.name){let{firstChange:o,previousValue:i}=e.name;if(o)return;this.isTrackedInParentContexts(i)&&(this.deactivate(),this.parentContexts.onChildOutletDestroyed(i)),this.initializeOutletWithName()}}ngOnDestroy(){this.isTrackedInParentContexts(this.name)&&this.parentContexts.onChildOutletDestroyed(this.name),this.inputBinder?.unsubscribeFromRouteData(this)}isTrackedInParentContexts(e){return this.parentContexts.getContext(e)?.outlet===this}ngOnInit(){this.initializeOutletWithName()}initializeOutletWithName(){if(this.parentContexts.onChildOutletCreated(this.name,this),this.activated)return;let e=this.parentContexts.getContext(this.name);e?.route&&(e.attachRef?this.attach(e.attachRef,e.route):this.activateWith(e.route,e.injector))}get isActivated(){return!!this.activated}get component(){if(!this.activated)throw new z(4012,!1);return this.activated.instance}get activatedRoute(){if(!this.activated)throw new z(4012,!1);return this._activatedRoute}get activatedRouteData(){return this._activatedRoute?this._activatedRoute.snapshot.data:{}}detach(){if(!this.activated)throw new z(4012,!1);this.location.detach();let e=this.activated;return this.activated=null,this._activatedRoute=null,this.detachEvents.emit(e.instance),e}attach(e,o){this.activated=e,this._activatedRoute=o,this.location.insert(e.hostView),this.inputBinder?.bindActivatedRouteToOutletComponent(this,this.location.injector),this.attachEvents.emit(e.instance)}deactivate(){if(this.activated){let e=this.component;this.activated.destroy(),this.activated=null,this._activatedRoute=null,this.deactivateEvents.emit(e)}}activateWith(e,o){if(this.isActivated)throw new z(4013,!1);this._activatedRoute=e;let i=this.location,a=e.snapshot.component,s=this.parentContexts.getOrCreateContext(this.name).children,c=new If(e,s,i.injector,this.routerOutletData);this.activated=i.createComponent(a,{index:i.length,injector:c,environmentInjector:o}),this.changeDetector.markForCheck(),this.inputBinder?.bindActivatedRouteToOutletComponent(this,this.location.injector),this.activateEvents.emit(this.activated.instance)}static \u0275fac=function(o){return new(o||n)};static \u0275dir=qe({type:n,selectors:[["router-outlet"]],inputs:{name:"name",routerOutletData:[1,"routerOutletData"]},outputs:{activateEvents:"activate",deactivateEvents:"deactivate",attachEvents:"attach",detachEvents:"detach"},exportAs:["outlet"],features:[In]})}return n})(),If=class{route;childContexts;parent;outletData;constructor(t,e,o,i){this.route=t,this.childContexts=e,this.parent=o,this.outletData=i}get(t,e){return t===lo?this.route:t===Nr?this.childContexts:t===pb?this.outletData:this.parent.get(t,e)}},vd=new B("");var Bf=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275cmp=Ge({type:n,selectors:[["ng-component"]],exportAs:["emptyRouterOutlet"],decls:1,vars:0,template:function(o,i){o&1&&$(0,"router-outlet")},dependencies:[hs],encapsulation:2,changeDetection:1})}return n})();function Hf(n){let t=n.children&&n.children.map(Hf),e=t?F(S({},n),{children:t}):S({},n);return!e.component&&!e.loadComponent&&(t||e.loadChildren)&&e.outlet&&e.outlet!==re&&(e.component=Bf),e}function p2(n,t,e){let o=new Set,i=fs(n,t._root,e?e._root:void 0,o);return{newlyCreatedRoutes:o,state:new gs(i,t)}}function fs(n,t,e,o){if(e&&n.shouldReuseRoute(t.value,e.value.snapshot)){let i=e.value;i._setPending(t.value);let r=f2(n,t,e,o);return new Vt(i,r)}else{if(n.shouldAttach(t.value)){let a=n.retrieve(t.value);if(a!==null){let s=a.route;return s.value._setPending(t.value),s.children=t.children.map(c=>fs(n,c,void 0,o)),s}}let i=m2(t.value);i._setPending(t.value),o.add(i);let r=t.children.map(a=>fs(n,a,void 0,o));return new Vt(i,r)}}function f2(n,t,e,o){return t.children.map(i=>{for(let r of e.children)if(n.shouldReuseRoute(i.value,r.value.snapshot))return fs(n,i,r,o);return fs(n,i,void 0,o)})}function m2(n){return new lo(new st(n.url),new st(n.params),new st(n.queryParams),new st(n.fragment),new st(n.data),n.outlet,n.component,n)}var Ir=class{redirectTo;navigationBehaviorOptions;constructor(t,e){this.redirectTo=t,this.navigationBehaviorOptions=e}},fb="ngNavigationCancelingError";function bd(n,t){let{redirectTo:e,navigationBehaviorOptions:o}=Vo(t)?{redirectTo:t,navigationBehaviorOptions:void 0}:t,i=mb(!1,pt.Redirect);return i.url=e,i.navigationBehaviorOptions=o,i}function mb(n,t){let e=new Error(`NavigationCancelingError: ${n||""}`);return e[fb]=!0,e.cancellationCode=t,e}function _2(n){return _b(n)&&Vo(n.url)}function _b(n){return!!n&&n[fb]}var Tf=class{routeReuseStrategy;futureState;currState;forwardEvent;inputBindingEnabled;constructor(t,e,o,i,r){this.routeReuseStrategy=t,this.futureState=e,this.currState=o,this.forwardEvent=i,this.inputBindingEnabled=r}activate(t){let e=this.futureState._root,o=this.currState?this.currState._root:null;this.deactivateChildRoutes(e,o,t),Pf(this.futureState.root),this.activateChildRoutes(e,o,t)}deactivateChildRoutes(t,e,o){let i=yr(e);t.children.forEach(r=>{let a=r.value.outlet;this.deactivateRoutes(r,i[a],o),delete i[a]}),Object.values(i).forEach(r=>{this.deactivateRouteAndItsChildren(r,o)})}deactivateRoutes(t,e,o){let i=t.value,r=e?e.value:null;if(i===r)if(i.component){let a=o.getContext(i.outlet);a&&this.deactivateChildRoutes(t,e,a.children)}else this.deactivateChildRoutes(t,e,o);else r&&this.deactivateRouteAndItsChildren(e,o)}deactivateRouteAndItsChildren(t,e){t.value.component&&this.routeReuseStrategy.shouldDetach(t.value.snapshot)?this.detachAndStoreRouteSubtree(t,e):this.deactivateRouteAndOutlet(t,e)}detachAndStoreRouteSubtree(t,e){let o=e.getContext(t.value.outlet),i=o&&t.value.component?o.children:e,r=yr(t);for(let a of Object.values(r))this.deactivateRouteAndItsChildren(a,i);if(o&&o.outlet){let a=o.outlet.detach(),s=o.children.onOutletDeactivated();this.routeReuseStrategy.store(t.value.snapshot,{componentRef:a,route:t,contexts:s})}}deactivateRouteAndOutlet(t,e){let o=e.getContext(t.value.outlet),i=o&&t.value.component?o.children:e,r=yr(t);for(let a of Object.values(r))this.deactivateRouteAndItsChildren(a,i);o&&(o.outlet&&(o.outlet.deactivate(),o.children.onOutletDeactivated()),o.attachRef=null,o.route=null),t.value._localInjector?.destroy()}activateChildRoutes(t,e,o){let i=yr(e);t.children.forEach(r=>{this.activateRoutes(r,i[r.value.outlet],o),this.forwardEvent(new hd(r.value.snapshot))}),t.children.length&&this.forwardEvent(new md(t.value.snapshot))}activateRoutes(t,e,o){let i=t.value,r=e?e.value:null;if(Pf(i),i===r)if(i.component){let a=o.getOrCreateContext(i.outlet);this.activateChildRoutes(t,e,a.children)}else this.activateChildRoutes(t,e,o);else if(i.component){let a=o.getOrCreateContext(i.outlet);if(this.routeReuseStrategy.shouldAttach(i.snapshot)){let s=this.routeReuseStrategy.retrieve(i.snapshot);this.routeReuseStrategy.store(i.snapshot,null),a.children.onOutletReAttached(s.contexts),a.attachRef=s.componentRef,a.route=s.route.value,a.outlet&&a.outlet.attach(s.componentRef,s.route.value),Pf(s.route.value),this.activateChildRoutes(t,null,a.children)}else a.attachRef=null,a.route=i,a.outlet&&a.outlet.activateWith(i,a.injector),this.activateChildRoutes(t,null,a.children)}else this.activateChildRoutes(t,null,o)}},Pd=class{path;route;constructor(t){this.path=t,this.route=this.path[this.path.length-1]}},wr=class{component;route;constructor(t,e){this.component=t,this.route=e}};function h2(n,t,e){let o=n._root,i=t?t._root:null;return is(o,i,e,[o.value])}function C2(n){let t=n.routeConfig?n.routeConfig.canActivateChild:null;return!t||t.length===0?null:{node:n,guards:t}}function Rr(n,t){let e=Symbol(),o=t.get(n,e);return o===e?typeof n=="function"&&!Vu(n)?n:t.get(n):o}function is(n,t,e,o,i={canDeactivateChecks:[],canActivateChecks:[]}){let r=yr(t);return n.children.forEach(a=>{M2(a,r[a.value.outlet],e,o.concat([a.value]),i),delete r[a.value.outlet]}),Object.entries(r).forEach(([a,s])=>ss(s,e.getContext(a),e,i)),i}function M2(n,t,e,o,i={canDeactivateChecks:[],canActivateChecks:[]}){let r=n.value,a=t?t.value:null,s=e?e.getContext(n.value.outlet):null;if(a&&r.routeConfig===a.routeConfig){let c=b2(a,r,r.routeConfig.runGuardsAndResolvers);c?i.canActivateChecks.push(new Pd(o)):(r.data=a.data,r._resolvedData=a._resolvedData),r.component?is(n,t,s?s.children:null,o,i):is(n,t,e,o,i),c&&s&&s.outlet&&s.outlet.isActivated&&i.canDeactivateChecks.push(new wr(s.outlet.component,a))}else a&&ss(t,s,e,i),i.canActivateChecks.push(new Pd(o)),r.component?is(n,null,s?s.children:null,o,i):is(n,null,e,o,i);return i}function b2(n,t,e){if(typeof e=="function")return et(t._environmentInjector,()=>e(n,t));switch(e){case"pathParamsChange":return!Di(n.url,t.url);case"pathParamsOrQueryParamsChange":return!Di(n.url,t.url)||!Ln(n.queryParams,t.queryParams);case"always":return!0;case"paramsOrQueryParamsChange":return!Df(n,t)||!Ln(n.queryParams,t.queryParams);default:return!Df(n,t)}}function ss(n,t,e,o){let i=yr(n),r=n.value;Object.entries(i).forEach(([a,s])=>{r.component?t?ss(s,t.children.getContext(a),t.children,o):ss(s,null,null,o):ss(s,e?e.getContext(a):null,e,o)}),r.component?t&&t.outlet&&t.outlet.isActivated?o.canDeactivateChecks.push(new wr(t.outlet.component,r)):o.canDeactivateChecks.push(new wr(null,r)):o.canDeactivateChecks.push(new wr(null,r))}function Cs(n){return typeof n=="function"}function P2(n){return typeof n=="boolean"}function O2(n){return n&&Cs(n.canLoad)}function v2(n){return n&&Cs(n.canActivate)}function y2(n){return n&&Cs(n.canActivateChild)}function x2(n){return n&&Cs(n.canDeactivate)}function w2(n){return n&&Cs(n.canMatch)}function hb(n){return n instanceof Qo||n?.name==="EmptyError"}var td=Symbol("INITIAL_VALUE");function Tr(){return vt(n=>Mu(n.map(t=>t.pipe(Bn(1),vu(td)))).pipe(Pe(t=>{for(let e of t)if(e!==!0){if(e===td)return td;if(e===!1||S2(e))return e}return!0}),Bt(t=>t!==td),Bn(1)))}function S2(n){return Vo(n)||n instanceof Ir}function Cb(n){return n.aborted?se(void 0).pipe(Bn(1)):new ie(t=>{let e=()=>{t.next(),t.complete()};return n.addEventListener("abort",e),()=>n.removeEventListener("abort",e)})}function Mb(n){return Xr(Cb(n))}function k2(n){return ut(t=>{let{targetSnapshot:e,currentSnapshot:o,guards:{canActivateChecks:i,canDeactivateChecks:r}}=t;return r.length===0&&i.length===0?se(F(S({},t),{guardsResult:!0})):E2(r,e,o).pipe(ut(a=>a&&P2(a)?D2(e,i,n):se(a)),Pe(a=>F(S({},t),{guardsResult:a})))})}function E2(n,t,e){return Fe(n).pipe(ut(o=>R2(o.component,o.route,e,t)),Hn(o=>o!==!0,!0))}function D2(n,t,e){return Fe(t).pipe(Jo(o=>Wi(T2(o.route.parent,e),I2(o.route,e),N2(n,o.path),A2(n,o.route))),Hn(o=>o!==!0,!0))}function I2(n,t){return n!==null&&t&&t(new _d(n)),se(!0)}function T2(n,t){return n!==null&&t&&t(new fd(n)),se(!0)}function A2(n,t){let e=t.routeConfig?t.routeConfig.canActivate:null;if(!e||e.length===0)return se(!0);let o=e.map(i=>Jr(()=>{let r=t._environmentInjector,a=Rr(i,r),s=v2(a)?a.canActivate(t,n):et(r,()=>a(t,n));return Ni(s).pipe(Hn())}));return se(o).pipe(Tr())}function N2(n,t){let e=t[t.length-1],i=t.slice(0,t.length-1).reverse().map(r=>C2(r)).filter(r=>r!==null).map(r=>Jr(()=>{let a=r.guards.map(s=>{let c=r.node._environmentInjector,p=Rr(s,c),f=y2(p)?p.canActivateChild(e,n):et(c,()=>p(e,n));return Ni(f).pipe(Hn())});return se(a).pipe(Tr())}));return se(i).pipe(Tr())}function R2(n,t,e,o){let i=t&&t.routeConfig?t.routeConfig.canDeactivate:null;if(!i||i.length===0)return se(!0);let r=i.map(a=>{let s=t._environmentInjector,c=Rr(a,s),p=x2(c)?c.canDeactivate(n,t,e,o):et(s,()=>c(n,t,e,o));return Ni(p).pipe(Hn())});return se(r).pipe(Tr())}function F2(n,t,e,o,i){let r=t.canLoad;if(r===void 0||r.length===0)return se(!0);let a=r.map(s=>{let c=Rr(s,n),p=O2(c)?c.canLoad(t,e):et(n,()=>c(t,e)),f=Ni(p);return i?f.pipe(Mb(i)):f});return se(a).pipe(Tr(),bb(o))}function bb(n){return $s(xe(t=>{if(typeof t!="boolean")throw bd(n,t)}),Pe(t=>t===!0))}function L2(n,t,e,o,i,r){let a=t.canMatch;if(!a||a.length===0)return se(!0);let s=a.map(c=>{let p=Rr(c,n),f=w2(p)?p.canMatch(t,e,i):et(n,()=>p(t,e,i));return Ni(f).pipe(Mb(r))});return se(s).pipe(Tr(),bb(o))}var so=class n extends Error{segmentGroup;constructor(t){super(),this.segmentGroup=t||null,Object.setPrototypeOf(this,n.prototype)}},ms=class n extends Error{urlTree;constructor(t){super(),this.urlTree=t,Object.setPrototypeOf(this,n.prototype)}};function V2(n){throw new z(4e3,!1)}function j2(n){throw mb(!1,pt.GuardRejected)}var Af=class{urlSerializer;urlTree;constructor(t,e){this.urlSerializer=t,this.urlTree=e}lineralizeSegments(t,e){return be(this,null,function*(){let o=[],i=e.root;for(;;){if(o=o.concat(i.segments),i.numberOfChildren===0)return o;if(i.numberOfChildren>1||!i.children[re])throw V2(`${t.redirectTo}`);i=i.children[re]}})}applyRedirectCommands(t,e,o,i,r){return be(this,null,function*(){let a=yield $2(e,i,r);if(a instanceof bt)throw new ms(a);let s=this.applyRedirectCreateUrlTree(a,this.urlSerializer.parse(a),t,o);if(a[0]==="/")throw new ms(s);return s})}applyRedirectCreateUrlTree(t,e,o,i){let r=this.createSegmentGroup(t,e.root,o,i);return new bt(r,this.createQueryParams(e.queryParams,this.urlTree.queryParams),e.fragment)}createQueryParams(t,e){let o={};return Object.entries(t).forEach(([i,r])=>{if(typeof r=="string"&&r[0]===":"){let s=r.substring(1);o[i]=e[s]}else o[i]=r}),o}createSegmentGroup(t,e,o,i){let r=this.createSegments(t,e.segments,o,i),a=Object.create(null);return Object.entries(e.children).forEach(([s,c])=>{a[s]=this.createSegmentGroup(t,c,o,i)}),new me(r,a)}createSegments(t,e,o,i){return e.map(r=>r.path[0]===":"?this.findPosParam(t,r,i):this.findOrReturn(r,o))}findPosParam(t,e,o){let i=o[e.path.substring(1)];if(!i)throw new z(4001,!1);return i}findOrReturn(t,e){let o=0;for(let i of e){if(i.path===t.path)return e.splice(o),i;o++}return t}};function $2(n,t,e){if(typeof n=="string")return Promise.resolve(n);let o=n;return rd(Ni(et(e,()=>o(t))))}function z2(n,t){return n.providers&&!n._injector&&(n._injector=Va(n.providers,t,`Route: ${n.path}`)),n._injector??t}function fn(n){return n.outlet||re}function B2(n,t){let e=n.filter(o=>fn(o)===t);return e.push(...n.filter(o=>fn(o)!==t)),e}var Nf={matched:!1,consumedSegments:[],remainingSegments:[],parameters:{},positionalParamSegments:{}};function Pb(n){return{routeConfig:n.routeConfig,url:n.url,params:n.params,queryParams:n.queryParams,fragment:n.fragment,data:n.data,outlet:n.outlet,title:n.title,paramMap:n.paramMap,queryParamMap:n.queryParamMap}}function H2(n,t,e,o,i,r,a){let s=Ob(n,t,e);if(!s.matched)return se(s);let c=Pb(r(s));return o=z2(t,o),L2(o,t,e,i,c,a).pipe(Pe(p=>p===!0?s:S({},Nf)))}function Ob(n,t,e){if(t.path==="")return t.pathMatch==="full"&&(n.hasChildren()||e.length>0)?S({},Nf):{matched:!0,consumedSegments:[],remainingSegments:e,parameters:{},positionalParamSegments:{}};let i=(t.matcher||W0)(e,n,t);if(!i)return S({},Nf);let r={};Object.entries(i.posParams??{}).forEach(([s,c])=>{r[s]=c.path});let a=i.consumed.length>0?S(S({},r),i.consumed[i.consumed.length-1].parameters):r;return{matched:!0,consumedSegments:i.consumed,remainingSegments:e.slice(i.consumed.length),parameters:a,positionalParamSegments:i.posParams??{}}}function G0(n,t,e,o,i){return e.length>0&&q2(n,e,o,i)?{segmentGroup:new me(t,G2(o,new me(e,n.children))),slicedSegments:[]}:e.length===0&&W2(n,e,o)?{segmentGroup:new me(n.segments,U2(n,e,o,n.children)),slicedSegments:e}:{segmentGroup:new me(n.segments,n.children),slicedSegments:e}}function U2(n,t,e,o){let i={};for(let r of e)if(yd(n,t,r)&&!o[fn(r)]){let a=new me([],{});i[fn(r)]=a}return S(S({},o),i)}function G2(n,t){let e={};e[re]=t;for(let o of n)if(o.path===""&&fn(o)!==re){let i=new me([],{});e[fn(o)]=i}return e}function q2(n,t,e,o){return e.some(i=>!yd(n,t,i)||!(fn(i)!==re)?!1:!(o!==void 0&&fn(i)===o))}function W2(n,t,e){return e.some(o=>yd(n,t,o))}function yd(n,t,e){return(n.hasChildren()||t.length>0)&&e.pathMatch==="full"?!1:e.path===""}function K2(n,t,e){return t.length===0&&!n.children[e]}var Rf=class{};function Y2(n,t,e,o,i,r,a,s){return be(this,null,function*(){return new Ff(n,t,e,o,i,a,r,s).recognize()})}var Z2=31,Ff=class{injector;configLoader;rootComponentType;config;urlTree;paramsInheritanceStrategy;urlSerializer;abortSignal;applyRedirects;absoluteRedirectCount=0;allowRedirects=!0;constructor(t,e,o,i,r,a,s,c){this.injector=t,this.configLoader=e,this.rootComponentType=o,this.config=i,this.urlTree=r,this.paramsInheritanceStrategy=a,this.urlSerializer=s,this.abortSignal=c,this.applyRedirects=new Af(this.urlSerializer,this.urlTree)}noMatchError(t){return new z(4002,`'${t.segmentGroup}'`)}recognize(){return be(this,null,function*(){let t=G0(this.urlTree.root,[],[],this.config).segmentGroup,{children:e,rootSnapshot:o}=yield this.match(t),i=new Vt(o,e),r=new ps("",i),a=ib(o,[],this.urlTree.queryParams,this.urlTree.fragment);return a.queryParams=this.urlTree.queryParams,r.url=this.urlSerializer.serialize(a),{state:r,tree:a}})}match(t){return be(this,null,function*(){let e=new Dr([],Object.freeze({}),Object.freeze(S({},this.urlTree.queryParams)),this.urlTree.fragment,Object.freeze({}),re,this.rootComponentType,null,{},this.injector);try{return{children:yield this.processSegmentGroup(this.injector,this.config,t,re,e),rootSnapshot:e}}catch(o){if(o instanceof ms)return this.urlTree=o.urlTree,this.match(o.urlTree.root);throw o instanceof so?this.noMatchError(o):o}})}processSegmentGroup(t,e,o,i,r){return be(this,null,function*(){if(o.segments.length===0&&o.hasChildren())return this.processChildren(t,e,o,r);let a=yield this.processSegment(t,e,o,o.segments,i,!0,r);return a instanceof Vt?[a]:[]})}processChildren(t,e,o,i){return be(this,null,function*(){let r=[];for(let c of Object.keys(o.children))c==="primary"?r.unshift(c):r.push(c);let a=[];for(let c of r){let p=o.children[c],f=B2(e,c),_=yield this.processSegmentGroup(t,f,p,c,i);a.push(..._)}let s=vb(a);return Q2(s),s})}processSegment(t,e,o,i,r,a,s){return be(this,null,function*(){for(let c of e)try{return yield this.processSegmentAgainstRoute(c._injector??t,e,c,o,i,r,a,s)}catch(p){if(p instanceof so||hb(p))continue;throw p}if(K2(o,i,r))return new Rf;throw new so(o)})}processSegmentAgainstRoute(t,e,o,i,r,a,s,c){return be(this,null,function*(){if(fn(o)!==a&&(a===re||!yd(i,r,o)))throw new so(i);if(o.redirectTo===void 0)return this.matchSegmentAgainstRoute(t,i,o,r,a,c);if(this.allowRedirects&&s)return this.expandSegmentAgainstRouteUsingRedirect(t,i,e,o,r,a,c);throw new so(i)})}expandSegmentAgainstRouteUsingRedirect(t,e,o,i,r,a,s){return be(this,null,function*(){let{matched:c,parameters:p,consumedSegments:f,positionalParamSegments:_,remainingSegments:C}=Ob(e,i,r);if(!c)throw new so(e);typeof i.redirectTo=="string"&&i.redirectTo[0]==="/"&&(this.absoluteRedirectCount++,this.absoluteRedirectCount>Z2&&(this.allowRedirects=!1));let h=this.createSnapshot(t,i,r,p,s);if(this.abortSignal.aborted)throw new Error(this.abortSignal.reason);let P=yield this.applyRedirects.applyRedirectCommands(f,i.redirectTo,_,Pb(h),t),T=yield this.applyRedirects.lineralizeSegments(i,P);return this.processSegment(t,o,e,T.concat(C),a,!1,s)})}createSnapshot(t,e,o,i,r){let a=new Dr(o,i,Object.freeze(S({},this.urlTree.queryParams)),this.urlTree.fragment,X2(e),fn(e),e.component??e._loadedComponent??null,e,eE(e),t),s=$f(a,r,this.paramsInheritanceStrategy);return a.params=Object.freeze(s.params),a.data=Object.freeze(s.data),a}matchSegmentAgainstRoute(t,e,o,i,r,a){return be(this,null,function*(){if(this.abortSignal.aborted)throw new Error(this.abortSignal.reason);let s=U=>this.createSnapshot(t,o,U.consumedSegments,U.parameters,a),c=yield rd(H2(e,o,i,t,this.urlSerializer,s,this.abortSignal));if(o.path==="**"&&(e.children={}),!c?.matched)throw new so(e);t=o._injector??t;let{routes:p}=yield this.getChildConfig(t,o,i),f=o._loadedInjector??t,{parameters:_,consumedSegments:C,remainingSegments:h}=c,P=this.createSnapshot(t,o,C,_,a),{segmentGroup:T,slicedSegments:A}=G0(e,C,h,p,r);if(A.length===0&&T.hasChildren()){let U=yield this.processChildren(f,p,T,P);return new Vt(P,U)}if(p.length===0&&A.length===0)return new Vt(P,[]);let N=fn(o)===r,E=yield this.processSegment(f,p,T,A,N?re:r,!0,P);return new Vt(P,E instanceof Vt?[E]:[])})}getChildConfig(t,e,o){return be(this,null,function*(){if(e.children)return{routes:e.children,injector:t};if(e.loadChildren){if(e._loadedRoutes!==void 0){let r=e._loadedNgModuleFactory;return r&&!e._loadedInjector&&(e._loadedInjector=r.create(t).injector),{routes:e._loadedRoutes,injector:e._loadedInjector}}if(this.abortSignal.aborted)throw new Error(this.abortSignal.reason);if(yield rd(F2(t,e,o,this.urlSerializer,this.abortSignal))){let r=yield this.configLoader.loadChildren(t,e);return e._loadedRoutes=r.routes,e._loadedInjector=r.injector,e._loadedNgModuleFactory=r.factory,r}throw j2(e)}return{routes:[],injector:t}})}};function Q2(n){n.sort((t,e)=>t.value.outlet===re?-1:e.value.outlet===re?1:t.value.outlet.localeCompare(e.value.outlet))}function J2(n){let t=n.value.routeConfig;return t&&t.path===""}function vb(n){let t=[],e=new Set;for(let o of n){if(!J2(o)){t.push(o);continue}let i=t.find(r=>o.value.routeConfig===r.value.routeConfig);i!==void 0?(i.children.push(...o.children),e.add(i)):t.push(o)}for(let o of e){let i=vb(o.children);t.push(new Vt(o.value,i))}return t.filter(o=>!e.has(o))}function X2(n){return n.data||{}}function eE(n){return n.resolve||{}}function tE(n,t,e,o,i,r,a){return ut(s=>be(null,null,function*(){let{state:c,tree:p}=yield Y2(n,t,e,o,s.extractedUrl,i,r,a);return F(S({},s),{targetSnapshot:c,urlAfterRedirects:p})}))}function nE(n){return ut(t=>{let{targetSnapshot:e,guards:{canActivateChecks:o}}=t;if(!o.length)return se(t);let i=new Set(o.map(s=>s.route)),r=new Set;for(let s of i)if(!r.has(s))for(let c of yb(s))r.add(c);let a=0;return Fe(r).pipe(Jo(s=>i.has(s)?oE(s,e,n):(s.data=$f(s,s.parent,n).resolve,se(void 0))),xe(()=>a++),ac(1),ut(s=>a===r.size?se(t):ct))})}function yb(n){let t=n.children.map(e=>yb(e)).flat();return[n,...t]}function oE(n,t,e){let o=n.routeConfig,i=n._resolve;return o?.title!==void 0&&!gb(o)&&(i[_s]=o.title),Jr(()=>(n.data=$f(n,n.parent,e).resolve,iE(i,n,t).pipe(Pe(r=>(n._resolvedData=r,n.data=S(S({},n.data),r),null)))))}function iE(n,t,e){let o=vf(n);if(o.length===0)return se({});let i={};return Fe(o).pipe(ut(r=>rE(n[r],t,e).pipe(Hn(),xe(a=>{if(a instanceof Ir)throw bd(new Lo,a);i[r]=a}))),ac(1),Pe(()=>i),Ki(r=>hb(r)?ct:Cu(r)))}function rE(n,t,e){let o=t._environmentInjector,i=Rr(n,o),r=i.resolve?i.resolve(t,e):et(o,()=>i(t,e));return Ni(r)}var xb=new B("");function Lf(n){return vt(t=>{let e=n(t);return e?Fe(e).pipe(Pe(()=>t)):se(t)})}var Uf=(()=>{class n{buildTitle(e){let o,i=e.root;for(;i!==void 0;)o=this.getResolvedTitleForRoute(i)??o,i=i.children.find(r=>r.outlet===re);return o}getResolvedTitleForRoute(e){return e.data[_s]}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:()=>D(wb)})}return n})(),wb=(()=>{class n extends Uf{title;constructor(e){super(),this.title=e}updateTitle(e){let o=this.buildTitle(e);o!==void 0&&this.title.setTitle(o)}static \u0275fac=function(o){return new(o||n)(Q($0))};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})}return n})(),Fr=new B("",{factory:()=>({})}),Ms=new B(""),Sb=(()=>{class n{componentLoaders=new WeakMap;childrenLoaders=new WeakMap;onLoadStartListener;onLoadEndListener;compiler=D(qp);loadComponent(e,o){return be(this,null,function*(){if(this.componentLoaders.get(o))return this.componentLoaders.get(o);if(o._loadedComponent)return Promise.resolve(o._loadedComponent);this.onLoadStartListener&&this.onLoadStartListener(o);let i=be(this,null,function*(){try{let r=yield Y0(et(e,()=>o.loadComponent())),a=yield Eb(Yp(r));return this.onLoadEndListener&&this.onLoadEndListener(o),o._loadedComponent=a,a}finally{this.componentLoaders.delete(o)}});return this.componentLoaders.set(o,i),i})}loadChildren(e,o){if(this.childrenLoaders.get(o))return this.childrenLoaders.get(o);if(o._loadedRoutes)return Promise.resolve({routes:o._loadedRoutes,injector:o._loadedInjector});this.onLoadStartListener&&this.onLoadStartListener(o);let i=be(this,null,function*(){try{let r=yield kb(o,this.compiler,e,this.onLoadEndListener);return o._loadedRoutes=r.routes,o._loadedInjector=r.injector,o._loadedNgModuleFactory=r.factory,r}finally{this.childrenLoaders.delete(o)}});return this.childrenLoaders.set(o,i),i}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function kb(n,t,e,o){return be(this,null,function*(){let i=yield Y0(et(e,()=>n.loadChildren())),r=yield Eb(Yp(i)),a;r instanceof wl||Array.isArray(r)?a=r:a=yield t.compileModuleAsync(r),o&&o(n);let s,c,p=!1,f;return Array.isArray(a)?(c=a,p=!0):(s=a.create(e).injector,f=a,c=s.get(Ms,[],{optional:!0,self:!0}).flat()),{routes:c.map(Hf),injector:s,factory:f}})}function Eb(n){return be(this,null,function*(){return n})}var xd=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:()=>D(aE)})}return n})(),aE=(()=>{class n{shouldProcessUrl(e){return!0}extract(e){return e}merge(e,o){return e}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),Db=new B("");var sE=()=>{},Ib=new B(""),Tb=(()=>{class n{currentNavigation=x(null,{equal:()=>!1});currentTransition=null;lastSuccessfulNavigation=x(null);events=new ye;transitionAbortWithErrorSubject=new ye;configLoader=D(Sb);environmentInjector=D(Le);destroyRef=D(Ae);urlSerializer=D(Ar);rootContexts=D(Nr);location=D(br);inputBindingEnabled=D(vd,{optional:!0})!==null;titleStrategy=D(Uf);options=D(Fr,{optional:!0})||{};paramsInheritanceStrategy=this.options.paramsInheritanceStrategy||g2;urlHandlingStrategy=D(xd);createViewTransition=D(Db,{optional:!0});navigationErrorHandler=D(Ib,{optional:!0});routerResourcesFeature=D(xb,{optional:!0});navigationId=0;get hasRequestedNavigation(){return this.navigationId!==0}transitions;afterPreactivation=()=>se(void 0);rootComponentType=null;destroyed=!1;constructor(){let e=i=>this.events.next(new gd(i)),o=i=>this.events.next(new pd(i));this.configLoader.onLoadEndListener=o,this.configLoader.onLoadStartListener=e,this.destroyRef.onDestroy(()=>{this.destroyed=!0})}complete(){this.transitions?.complete()}handleNavigationRequest(e){let o=++this.navigationId;de(()=>{this.transitions?.next(F(S({},e),{extractedUrl:this.urlHandlingStrategy.extract(e.rawUrl),targetSnapshot:null,targetRouterState:null,guards:{canActivateChecks:[],canDeactivateChecks:[]},guardsResult:null,id:o,routesRecognizeHandler:{},beforeActivateHandler:{}}))})}setupNavigations(e){return this.transitions=new st(null),this.transitions.pipe(Bt(o=>o!==null),vt(o=>{let i=!0,r=!1,a=new AbortController,s=()=>!r&&this.currentTransition?.id===o.id;return se(o).pipe(vt(c=>{if(this.navigationId>o.id)return this.cancelNavigationTransition(o,"",pt.SupersededByNewNavigation),ct;this.currentTransition=o;let p=this.lastSuccessfulNavigation();this.currentNavigation.set({id:c.id,initialUrl:c.rawUrl,extractedUrl:c.extractedUrl,targetBrowserUrl:typeof c.extras.browserUrl=="string"?this.urlSerializer.parse(c.extras.browserUrl):c.extras.browserUrl,trigger:c.source,extras:c.extras,previousNavigation:p?F(S({},p),{previousNavigation:null}):null,abort:()=>a.abort(),routesRecognizeHandler:c.routesRecognizeHandler,beforeActivateHandler:c.beforeActivateHandler});let f=!e.navigated||this.isUpdatingInternalState()||this.isUpdatedBrowserUrl(),_=c.extras.onSameUrlNavigation??e.onSameUrlNavigation;if(!f&&_!=="reload")return this.events.next(new co(c.id,this.urlSerializer.serialize(c.rawUrl),"",ls.IgnoredSameUrlNavigation)),c.resolve(!1),ct;if(this.urlHandlingStrategy.shouldProcessUrl(c.rawUrl))return se(c).pipe(vt(C=>(this.events.next(new Ti(C.id,this.urlSerializer.serialize(C.extractedUrl),C.source,C.restoredState)),C.id!==this.navigationId?ct:Promise.resolve(C))),tE(this.environmentInjector,this.configLoader,this.rootComponentType,e.config,this.urlSerializer,this.paramsInheritanceStrategy,a.signal),xe(C=>{o.targetSnapshot=C.targetSnapshot,o.urlAfterRedirects=C.urlAfterRedirects,this.currentNavigation.update(h=>(h.finalUrl=C.urlAfterRedirects,h)),this.events.next(new us)}),vt(C=>Fe(o.routesRecognizeHandler.deferredHandle??se(void 0)).pipe(Pe(()=>C))),xe(()=>{let C=new ds(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects),c.targetSnapshot);this.events.next(C)}));if(f&&this.urlHandlingStrategy.shouldProcessUrl(c.currentRawUrl)){let{id:C,extractedUrl:h,source:P,restoredState:T,extras:A}=c,N=new Ti(C,this.urlSerializer.serialize(h),P,T);this.events.next(N);let E=db(this.rootComponentType,this.environmentInjector).snapshot;return this.currentTransition=o=F(S({},c),{targetSnapshot:E,urlAfterRedirects:h,extras:F(S({},A),{skipLocationChange:!1,replaceUrl:!1})}),this.currentNavigation.update(U=>(U.finalUrl=h,U)),se(o)}else return this.events.next(new co(c.id,this.urlSerializer.serialize(c.extractedUrl),"",ls.IgnoredByUrlHandlingStrategy)),c.resolve(!1),ct}),Pe(c=>{let p=new cd(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects),c.targetSnapshot);return this.events.next(p),this.currentTransition=o=F(S({},c),{guards:h2(c.targetSnapshot,c.currentSnapshot,this.rootContexts)}),o}),k2(c=>this.events.next(c)),vt(c=>{if(o.guardsResult=c.guardsResult,c.guardsResult&&typeof c.guardsResult!="boolean")throw bd(this.urlSerializer,c.guardsResult);let p=new ld(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects),c.targetSnapshot,!!c.guardsResult);if(this.events.next(p),!s())return ct;if(!c.guardsResult)return this.cancelNavigationTransition(c,"",pt.GuardRejected),ct;if(c.guards.canActivateChecks.length===0)return se(c);let f=new dd(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects),c.targetSnapshot);if(this.events.next(f),!s())return ct;let _=!1;return se(c).pipe(nE(this.paramsInheritanceStrategy),xe({next:()=>{_=!0;let C=new ud(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects),c.targetSnapshot);this.events.next(C)},complete:()=>{_||this.cancelNavigationTransition(c,"",pt.NoDataFromResolver)}}))}),Lf(c=>{let p=_=>{let C=[];if(_.routeConfig?._loadedComponent)_.component=_.routeConfig?._loadedComponent;else if(_.routeConfig?.loadComponent){let h=_._environmentInjector;C.push(this.configLoader.loadComponent(h,_.routeConfig).then(P=>{_.component=P}))}for(let h of _.children)C.push(...p(h));return C},f=p(c.targetSnapshot.root);return f.length===0?se(c):Fe(Promise.all(f).then(()=>c))}),vt(c=>{let{newlyCreatedRoutes:p,state:f}=p2(e.routeReuseStrategy,c.targetSnapshot,c.currentRouterState);return this.currentTransition=o=c=F(S({},c),{targetRouterState:f,newlyCreatedRoutes:p}),this.currentNavigation.update(_=>(_.targetRouterState=f,_)),se(c)}),this.routerResourcesFeature?.setupAndRunResources(a.signal)??(c=>c),Lf(()=>this.afterPreactivation()),vt(()=>{let{currentSnapshot:c,targetSnapshot:p}=o,f=this.createViewTransition?.(this.environmentInjector,c.root,p.root);return f?Fe(f).pipe(Pe(()=>o)):se(o)}),Bn(1),vt(c=>{i=!1,this.events.next(new kr);let p=o.beforeActivateHandler.deferredHandle;return p?Fe(p.then(()=>c)):se(c)}),xe(c=>{new Tf(e.routeReuseStrategy,o.targetRouterState,o.currentRouterState,p=>this.events.next(p),this.inputBindingEnabled).activate(this.rootContexts),c.newlyCreatedRoutes?.clear(),s()&&(Ab(c.targetRouterState),r=!0,this.currentNavigation.update(p=>(p.abort=sE,p)),this.lastSuccessfulNavigation.set(de(this.currentNavigation)),this.events.next(new mn(c.id,this.urlSerializer.serialize(c.extractedUrl),this.urlSerializer.serialize(c.urlAfterRedirects))),this.titleStrategy?.updateTitle(c.targetRouterState.snapshot),c.resolve(!0))}),Xr(Cb(a.signal).pipe(Bt(()=>!r&&i),xe(()=>{this.cancelNavigationTransition(o,a.signal.reason+"",pt.Aborted)}))),xe({complete:()=>{r=!0}}),Xr(this.transitionAbortWithErrorSubject.pipe(xe(c=>{throw c}))),_o(()=>{a.abort(),r||this.cancelNavigationTransition(o,"",pt.SupersededByNewNavigation),this.currentTransition?.id===o.id&&(this.currentNavigation.set(null),this.currentTransition=null)}),Ki(c=>{if(r=!0,q0(o),this.destroyed)return o.resolve(!1),ct;if(_b(c))this.events.next(new Zt(o.id,this.urlSerializer.serialize(o.extractedUrl),c.message,c.cancellationCode)),_2(c)?this.events.next(new Er(c.url,c.navigationBehaviorOptions)):o.resolve(!1);else{let p=new Ai(o.id,this.urlSerializer.serialize(o.extractedUrl),c,o.targetSnapshot??void 0);try{let f=et(this.environmentInjector,()=>this.navigationErrorHandler?.(p));if(f instanceof Ir){let{message:_,cancellationCode:C}=bd(this.urlSerializer,f);this.events.next(new Zt(o.id,this.urlSerializer.serialize(o.extractedUrl),_,C)),this.events.next(new Er(f.redirectTo,f.navigationBehaviorOptions))}else throw this.events.next(p),c}catch(f){this.options.resolveNavigationPromiseOnError?o.resolve(!1):o.reject(f)}}return ct}))}))}cancelNavigationTransition(e,o,i){q0(e);let r=new Zt(e.id,this.urlSerializer.serialize(e.extractedUrl),o,i);this.events.next(r),e.resolve(!1)}isUpdatingInternalState(){return this.currentTransition?.extractedUrl.toString()!==this.currentTransition?.currentUrlTree.toString()}isUpdatedBrowserUrl(){let e=this.urlHandlingStrategy.extract(this.urlSerializer.parse(this.location.path(!0))),o=de(this.currentNavigation),i=o?.targetBrowserUrl??o?.extractedUrl;return e.toString()!==i?.toString()&&!o?.extras.skipLocationChange}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function cE(n){return n!==as}function q0(n){for(let t of n.newlyCreatedRoutes??[])t._localInjector?.destroy(),t._localInjector=void 0;Ab(n.targetRouterState)}function Ab(n){if(!n)return;let t=e=>{e.value.pending?.set(!1),e.children.forEach(t)};t(n._root)}var Nb=new B("");var Rb=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:()=>D(lE)})}return n})(),Od=class{shouldDetach(t){return!1}store(t,e){}shouldAttach(t){return!1}retrieve(t){return null}shouldReuseRoute(t,e){return t.routeConfig===e.routeConfig}shouldDestroyInjector(t){return!0}},lE=(()=>{class n extends Od{static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),wd=(()=>{class n{urlSerializer=D(Ar);options=D(Fr,{optional:!0})||{};canceledNavigationResolution=this.options.canceledNavigationResolution||"replace";location=D(br);urlHandlingStrategy=D(xd);urlUpdateStrategy=this.options.urlUpdateStrategy||"deferred";currentUrlTree=new bt;getCurrentUrlTree(){return this.currentUrlTree}rawUrlTree=this.currentUrlTree;getRawUrlTree(){return this.rawUrlTree}createBrowserPath({finalUrl:e,initialUrl:o,targetBrowserUrl:i}){let r=e!==void 0?this.urlHandlingStrategy.merge(e,o):o,a=i??r;return a instanceof bt?this.urlSerializer.serialize(a):a}routerUrlState(e){return e?.targetBrowserUrl===void 0||e?.finalUrl===void 0?{}:{\u0275routerUrl:this.urlSerializer.serialize(e.finalUrl)}}commitTransition({targetRouterState:e,finalUrl:o,initialUrl:i}){o&&e?(this.currentUrlTree=o,this.rawUrlTree=this.urlHandlingStrategy.merge(o,i),this.routerState=e):this.rawUrlTree=i}routerState=db(null,D(Le));getRouterState(){return this.routerState}_stateMemento=this.createStateMemento();get stateMemento(){return this._stateMemento}updateStateMemento(){this._stateMemento=this.createStateMemento()}createStateMemento(){return{rawUrlTree:this.rawUrlTree,currentUrlTree:this.currentUrlTree,routerState:this.routerState}}restoredState(){return this.location.getState()}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:()=>D(dE)})}return n})(),dE=(()=>{class n extends wd{currentPageId=0;lastSuccessfulId=-1;get browserPageId(){return this.canceledNavigationResolution!=="computed"?this.currentPageId:this.restoredState()?.\u0275routerPageId??this.currentPageId}registerNonRouterCurrentEntryChangeListener(e){return this.location.subscribe(o=>{o.type==="popstate"&&setTimeout(()=>{e(o.url,o.state,"popstate",{replaceUrl:!0})})})}handleRouterEvent(e,o){e instanceof Ti?this.updateStateMemento():e instanceof co?this.commitTransition(o):e instanceof ds?this.urlUpdateStrategy==="eager"&&(o.extras.skipLocationChange||this.setBrowserUrl(this.createBrowserPath(o),o)):e instanceof kr?(this.commitTransition(o),this.urlUpdateStrategy==="deferred"&&!o.extras.skipLocationChange&&this.setBrowserUrl(this.createBrowserPath(o),o)):e instanceof Zt&&!lb(e)?this.restoreHistory(o):e instanceof Ai?this.restoreHistory(o,!0):e instanceof mn&&(this.lastSuccessfulId=e.id,this.currentPageId=this.browserPageId)}setBrowserUrl(e,o){let{extras:i,id:r}=o,{replaceUrl:a,state:s}=i;if(this.location.isCurrentPathEqualTo(e)||a){let c=this.browserPageId,p=S(S({},s),this.generateNgRouterState(r,c,o));this.location.replaceState(e,"",p)}else{let c=S(S({},s),this.generateNgRouterState(r,this.browserPageId+1,o));this.location.go(e,"",c)}}restoreHistory(e,o=!1){if(this.canceledNavigationResolution==="computed"){let i=this.browserPageId,r=this.currentPageId-i;r!==0?this.location.historyGo(r):this.getCurrentUrlTree()===e.finalUrl&&r===0&&(this.resetInternalState(e),this.resetUrlToCurrentUrlTree())}else this.canceledNavigationResolution==="replace"&&(o&&this.resetInternalState(e),this.resetUrlToCurrentUrlTree())}resetInternalState({finalUrl:e}){this.routerState=this.stateMemento.routerState,this.currentUrlTree=this.stateMemento.currentUrlTree,this.rawUrlTree=this.urlHandlingStrategy.merge(this.currentUrlTree,e??this.rawUrlTree)}resetUrlToCurrentUrlTree(){this.location.replaceState(this.urlSerializer.serialize(this.getRawUrlTree()),"",this.generateNgRouterState(this.lastSuccessfulId,this.currentPageId))}generateNgRouterState(e,o,i){return this.canceledNavigationResolution==="computed"?S({navigationId:e,\u0275routerPageId:o},this.routerUrlState(i)):S({navigationId:e},this.routerUrlState(i))}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function Gf(n,t){n.events.pipe(Bt(e=>e instanceof mn||e instanceof Zt||e instanceof Ai||e instanceof co),Pe(e=>e instanceof mn||e instanceof co?0:(e instanceof Zt?e.code===pt.Redirect||e.code===pt.SupersededByNewNavigation:!1)?2:1),Bt(e=>e!==2),Bn(1)).subscribe(()=>{t()})}var uo=(()=>{class n{get currentUrlTree(){return this.stateManager.getCurrentUrlTree()}get rawUrlTree(){return this.stateManager.getRawUrlTree()}disposed=!1;nonRouterCurrentEntryChangeSubscription;console=D($p);stateManager=D(wd);options=D(Fr,{optional:!0})||{};pendingTasks=D(kn);urlUpdateStrategy=this.options.urlUpdateStrategy||"deferred";navigationTransitions=D(Tb);urlSerializer=D(Ar);location=D(br);urlHandlingStrategy=D(xd);injector=D(Le);_events=new ye;get events(){return this._events}get routerState(){return this.stateManager.getRouterState()}navigated=!1;routeReuseStrategy=D(Rb);injectorCleanup=D(Nb,{optional:!0});onSameUrlNavigation=this.options.onSameUrlNavigation||"ignore";config=D(Ms,{optional:!0})?.flat()??[];componentInputBindingEnabled=!!D(vd,{optional:!0});currentNavigation=this.navigationTransitions.currentNavigation.asReadonly();constructor(){this.resetConfig(this.config),this.navigationTransitions.setupNavigations(this).subscribe({error:e=>{}}),this.subscribeToNavigationEvents()}eventsSubscription=new Be;subscribeToNavigationEvents(){let e=this.navigationTransitions.events.subscribe(o=>{try{let i=this.navigationTransitions.currentTransition,r=de(this.navigationTransitions.currentNavigation);if(i!==null&&r!==null){if(this.stateManager.handleRouterEvent(o,r),o instanceof Zt&&o.code!==pt.Redirect&&o.code!==pt.SupersededByNewNavigation)this.navigated=!0;else if(o instanceof mn)this.navigated=!0,this.injectorCleanup?.(this.routeReuseStrategy,this.routerState,this.config);else if(o instanceof Er){let a=o.navigationBehaviorOptions,s=this.urlHandlingStrategy.merge(o.url,i.currentRawUrl),c=S({scroll:i.extras.scroll,browserUrl:i.extras.browserUrl,info:i.extras.info,skipLocationChange:i.extras.skipLocationChange,replaceUrl:i.extras.replaceUrl||this.urlUpdateStrategy==="eager"||cE(i.source)},a);this.scheduleNavigation(s,as,null,c,{resolve:i.resolve,reject:i.reject,promise:i.promise})}}d2(o)&&this._events.next(o)}catch(i){this.navigationTransitions.transitionAbortWithErrorSubject.next(i)}});this.eventsSubscription.add(e)}resetRootComponentType(e){this.routerState.root.component=e,this.navigationTransitions.rootComponentType=e}initialNavigation(){this.setUpLocationChangeListener(),this.navigationTransitions.hasRequestedNavigation||this.navigateToSyncWithBrowser(this.location.path(!0),as,this.stateManager.restoredState(),{replaceUrl:!0})}setUpLocationChangeListener(){this.nonRouterCurrentEntryChangeSubscription??=this.stateManager.registerNonRouterCurrentEntryChangeListener((e,o,i,r)=>{this.navigateToSyncWithBrowser(e,i,o,r)})}navigateToSyncWithBrowser(e,o,i,r){let a=i?.navigationId?i:null,s=i?.\u0275routerUrl??e;if(i?.\u0275routerUrl&&(r=F(S({},r),{browserUrl:e})),i){let p=S({},i);delete p.navigationId,delete p.\u0275routerPageId,delete p.\u0275routerUrl,Object.keys(p).length!==0&&(r.state=p)}let c=this.parseUrl(s);this.scheduleNavigation(c,o,a,r).catch(p=>{this.disposed||this.injector.get(Nt)(p)})}get url(){return this.serializeUrl(this.currentUrlTree)}getCurrentNavigation(){return de(this.navigationTransitions.currentNavigation)}get lastSuccessfulNavigation(){return this.navigationTransitions.lastSuccessfulNavigation}resetConfig(e){this.config=e.map(Hf),this.navigated=!1}ngOnDestroy(){this.dispose()}dispose(){this._events.unsubscribe(),this.navigationTransitions.complete(),this.nonRouterCurrentEntryChangeSubscription?.unsubscribe(),this.nonRouterCurrentEntryChangeSubscription=void 0,this.disposed=!0,this.eventsSubscription.unsubscribe()}createUrlTree(e,o={}){let{relativeTo:i,queryParams:r,fragment:a,queryParamsHandling:s,preserveFragment:c}=o,p=c?this.currentUrlTree.fragment:a,f=null;switch(s??this.options.defaultQueryParamsHandling){case"merge":f=S(S({},this.currentUrlTree.queryParams),r);break;case"preserve":f=this.currentUrlTree.queryParams;break;default:f=r||null}f!==null&&(f=this.removeEmptyProps(f));let _;try{let C=i?i.snapshot:this.routerState.snapshot.root;_=rb(C)}catch(C){(typeof e[0]!="string"||e[0][0]!=="/")&&(e=[]),_=this.currentUrlTree.root}return ab(_,e,f,p??null,this.urlSerializer)}navigateByUrl(e,o={skipLocationChange:!1}){let i=Vo(e)?e:this.parseUrl(e),r=this.urlHandlingStrategy.merge(i,this.rawUrlTree);return this.scheduleNavigation(r,as,null,o)}navigate(e,o={skipLocationChange:!1}){return uE(e),this.navigateByUrl(this.createUrlTree(e,o),o)}serializeUrl(e){return this.urlSerializer.serialize(e)}parseUrl(e){try{return this.urlSerializer.parse(e)}catch(o){return this.console.warn(Wn(4018,!1)),this.urlSerializer.parse("/")}}isActive(e,o){let i;if(o===!0?i=S({},Vf):o===!1?i=S({},Sr):i=S(S({},Sr),o),Vo(e))return yf(this.currentUrlTree,e,i);let r=this.parseUrl(e);return yf(this.currentUrlTree,r,i)}removeEmptyProps(e){return Object.entries(e).reduce((o,[i,r])=>(r!=null&&(o[i]=r),o),{})}scheduleNavigation(e,o,i,r,a){if(this.disposed)return Promise.resolve(!1);let s,c,p;a?(s=a.resolve,c=a.reject,p=a.promise):p=new Promise((_,C)=>{s=_,c=C});let f=this.pendingTasks.add();return Gf(this,()=>{queueMicrotask(()=>this.pendingTasks.remove(f))}),this.navigationTransitions.handleNavigationRequest({source:o,restoredState:i,currentUrlTree:this.currentUrlTree,currentRawUrl:this.currentUrlTree,rawUrl:e,extras:r,resolve:s,reject:c,promise:p,currentSnapshot:this.routerState.snapshot,currentRouterState:this.routerState}),p.catch(Promise.reject.bind(Promise))}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})();function uE(n){for(let t=0;t{class n{router=D(uo);stateManager=D(wd);fragment=x("");queryParams=x({});path=x("");serializer=D(Ar);constructor(){this.updateState(),this.router.events?.subscribe(e=>{e instanceof mn&&this.updateState()})}updateState(){let{fragment:e,root:o,queryParams:i}=this.stateManager.getCurrentUrlTree();this.fragment.set(e),this.queryParams.set(i),this.path.set(this.serializer.serialize(new bt(o)))}static \u0275fac=function(o){return new(o||n)};static \u0275prov=Oe({token:n,factory:n.\u0275fac})}return n})(),Lr=(()=>{class n{router;route;tabIndexAttribute;renderer;el;locationStrategy;hrefAttributeValue=D(new Tl("href"),{optional:!0});reactiveHref=Wp(()=>this.isAnchorElement?this.computeHref(this._urlTree()):this.hrefAttributeValue);get href(){return de(this.reactiveHref)}set href(e){this.reactiveHref.set(e)}set target(e){this._target.set(e)}get target(){return de(this._target)}_target=x(void 0);set queryParams(e){this._queryParams.set(e)}get queryParams(){return de(this._queryParams)}_queryParams=x(void 0,{equal:()=>!1});set fragment(e){this._fragment.set(e)}get fragment(){return de(this._fragment)}_fragment=x(void 0);set queryParamsHandling(e){this._queryParamsHandling.set(e)}get queryParamsHandling(){return de(this._queryParamsHandling)}_queryParamsHandling=x(void 0);set state(e){this._state.set(e)}get state(){return de(this._state)}_state=x(void 0,{equal:()=>!1});set info(e){this._info.set(e)}get info(){return de(this._info)}_info=x(void 0,{equal:()=>!1});set relativeTo(e){this._relativeTo.set(e)}get relativeTo(){return de(this._relativeTo)}_relativeTo=x(void 0);set preserveFragment(e){this._preserveFragment.set(e)}get preserveFragment(){return de(this._preserveFragment)}_preserveFragment=x(!1);set skipLocationChange(e){this._skipLocationChange.set(e)}get skipLocationChange(){return de(this._skipLocationChange)}_skipLocationChange=x(!1);set replaceUrl(e){this._replaceUrl.set(e)}get replaceUrl(){return de(this._replaceUrl)}_replaceUrl=x(!1);browserUrl=Al(void 0);isAnchorElement;onChanges=new ye;applicationErrorHandler=D(Nt);options=D(Fr,{optional:!0});reactiveRouterState=D(pE);constructor(e,o,i,r,a,s){this.router=e,this.route=o,this.tabIndexAttribute=i,this.renderer=r,this.el=a,this.locationStrategy=s;let c=a.nativeElement.tagName?.toLowerCase();this.isAnchorElement=c==="a"||c==="area"||!!(typeof customElements=="object"&&customElements.get(c)?.observedAttributes?.includes?.("href"))}setTabIndexIfNotOnNativeEl(e){this.tabIndexAttribute!=null||this.isAnchorElement||this.applyAttributeValue("tabindex",e)}ngOnChanges(e){this.onChanges.next(this)}routerLinkInput=x(null);set routerLink(e){e==null?(this.routerLinkInput.set(null),this.setTabIndexIfNotOnNativeEl(null)):(Vo(e)?this.routerLinkInput.set(e):this.routerLinkInput.set(Array.isArray(e)?e:[e]),this.setTabIndexIfNotOnNativeEl("0"))}onClick(e,o,i,r,a){let s=this._urlTree();if(s===null||this.isAnchorElement&&(e!==0||o||i||r||a||typeof this.target=="string"&&this.target!="_self"))return!0;let c=this.browserUrl(),p=S({skipLocationChange:this.skipLocationChange,replaceUrl:this.replaceUrl,state:this.state,info:this.info},c!==void 0&&{browserUrl:c});return this.router.navigateByUrl(s,p)?.catch(f=>{this.applicationErrorHandler(f)}),!this.isAnchorElement}ngOnDestroy(){}applyAttributeValue(e,o){let i=this.renderer,r=this.el.nativeElement;o!==null?i.setAttribute(r,e,o):i.removeAttribute(r,e)}_urlTree=I(()=>{this.reactiveRouterState.path(),this._preserveFragment()&&this.reactiveRouterState.fragment();let e=i=>i==="preserve"||i==="merge";(e(this._queryParamsHandling())||e(this.options?.defaultQueryParamsHandling))&&this.reactiveRouterState.queryParams();let o=this.routerLinkInput();return o===null||!this.router.createUrlTree?null:Vo(o)?o:this.router.createUrlTree(o,{relativeTo:this._relativeTo()!==void 0?this._relativeTo():this.route,queryParams:this._queryParams(),fragment:this._fragment(),queryParamsHandling:this._queryParamsHandling(),preserveFragment:this._preserveFragment()})},{equal:(e,o)=>this.computeHref(e)===this.computeHref(o)});get urlTree(){return de(this._urlTree)}computeHref(e){return e!==null&&this.locationStrategy?this.locationStrategy?.prepareExternalUrl(this.router.serializeUrl(e))??"":null}static \u0275fac=function(o){return new(o||n)(le(uo),le(lo),Ra("tabindex"),le(wt),le(Mt),le(Mr))};static \u0275dir=qe({type:n,selectors:[["","routerLink",""]],hostVars:2,hostBindings:function(o,i){o&1&&k("click",function(a){return i.onClick(a.button,a.ctrlKey,a.shiftKey,a.altKey,a.metaKey)}),o&2&&Wt("href",i.reactiveHref(),_p)("target",i._target())},inputs:{target:"target",queryParams:"queryParams",fragment:"fragment",queryParamsHandling:"queryParamsHandling",state:"state",info:"info",relativeTo:"relativeTo",preserveFragment:[2,"preserveFragment","preserveFragment",Si],skipLocationChange:[2,"skipLocationChange","skipLocationChange",Si],replaceUrl:[2,"replaceUrl","replaceUrl",Si],browserUrl:[1,"browserUrl"],routerLink:"routerLink"},features:[In]})}return n})(),qf=(()=>{class n{router;element;renderer;cdr;links;classes=[];routerEventsSubscription;linkInputChangesSubscription;_isActive=!1;get isActive(){return this._isActive}routerLinkActiveOptions={exact:!1};ariaCurrentWhenActive;isActiveChange=new Ke;link=D(Lr,{optional:!0});constructor(e,o,i,r){this.router=e,this.element=o,this.renderer=i,this.cdr=r,this.routerEventsSubscription=e.events.subscribe(a=>{a instanceof mn&&this.update()})}ngAfterContentInit(){se(this.links.changes,se(null)).pipe(qi()).subscribe(e=>{this.update(),this.subscribeToEachLinkOnChanges()})}subscribeToEachLinkOnChanges(){this.linkInputChangesSubscription?.unsubscribe();let e=[...this.links.toArray(),this.link].filter(o=>!!o).map(o=>o.onChanges);this.linkInputChangesSubscription=Fe(e).pipe(qi()).subscribe(o=>{this._isActive!==this.isLinkActive(this.router)(o)&&this.update()})}set routerLinkActive(e){if(e==null){this.classes=[];return}let o=Array.isArray(e)?e:e.split(" ");this.classes=o.filter(i=>!!i)}ngOnChanges(e){this.update()}ngOnDestroy(){this.routerEventsSubscription.unsubscribe(),this.linkInputChangesSubscription?.unsubscribe()}update(){!this.links||!this.router.navigated||this.routerLinkActiveOptions===null&&!this._isActive||queueMicrotask(()=>{let e=this.hasActiveLinks();this.classes.forEach(o=>{e?this.renderer.addClass(this.element.nativeElement,o):this.renderer.removeClass(this.element.nativeElement,o)}),e&&this.ariaCurrentWhenActive!==void 0?this.renderer.setAttribute(this.element.nativeElement,"aria-current",this.ariaCurrentWhenActive.toString()):this.renderer.removeAttribute(this.element.nativeElement,"aria-current"),this._isActive!==e&&(this._isActive=e,this.cdr.markForCheck(),this.isActiveChange.emit(e))})}isLinkActive(e){let o=this.routerLinkActiveOptions;if(o===null)return()=>!1;let i;return o===void 0?i=S({},Sr):fE(o)?i=o:o.exact??!1?i=S({},Vf):i=S({},Sr),r=>{let a=r.urlTree;return a?de(jf(a,e,i)):!1}}hasActiveLinks(){let e=this.isLinkActive(this.router);return this.link&&e(this.link)||this.links.some(e)}static \u0275fac=function(o){return new(o||n)(le(uo),le(Mt),le(wt),le(oo))};static \u0275dir=qe({type:n,selectors:[["","routerLinkActive",""]],contentQueries:function(o,i,r){if(o&1&&El(r,Lr,5),o&2){let a;eo(a=to())&&(i.links=a)}},inputs:{routerLinkActiveOptions:"routerLinkActiveOptions",ariaCurrentWhenActive:"ariaCurrentWhenActive",routerLinkActive:"routerLinkActive"},outputs:{isActiveChange:"isActiveChange"},exportAs:["routerLinkActive"],features:[In]})}return n})();function fE(n){let t=n;return!!(t.paths||t.matrixParams||t.queryParams||t.fragment)}var mE=new B("");function Wf(n,...t){return bo([{provide:Ms,multi:!0,useValue:n},{provide:lo,useFactory:_E},{provide:kl,multi:!0,useFactory:hE},t.map(e=>e.\u0275providers)])}function _E(){return D(uo).routerState.root}function hE(){let n=D(Ct);return t=>{let e=n.get(Xn);if(t!==e.components[0])return;let o=n.get(uo),i=n.get(CE);n.get(ME)===1&&o.initialNavigation(),n.get(bE,null,{optional:!0})?.setUpPreloading(),n.get(mE,null,{optional:!0})?.init(),o.resetRootComponentType(e.componentTypes[0]),i.closed||(i.next(),i.complete(),i.unsubscribe())}}var CE=new B("",{factory:()=>new ye}),ME=new B("",{factory:()=>1});var bE=new B("");var Gb=(()=>{class n{_renderer;_elementRef;onChange=e=>{};onTouched=()=>{};constructor(e,o){this._renderer=e,this._elementRef=o}setProperty(e,o){this._renderer.setProperty(this._elementRef.nativeElement,e,o)}registerOnTouched(e){this.onTouched=e}registerOnChange(e){this.onChange=e}setDisabledState(e){this.setProperty("disabled",e)}static \u0275fac=function(o){return new(o||n)(le(wt),le(Mt))};static \u0275dir=qe({type:n})}return n})(),Td=(()=>{class n extends Gb{static \u0275fac=(()=>{let e;return function(i){return(e||(e=ko(n)))(i||n)}})();static \u0275dir=qe({type:n,features:[gn]})}return n})(),xs=new B(""),PE={provide:xs,useExisting:on(()=>em),multi:!0},em=(()=>{class n extends Td{writeValue(e){this.setProperty("checked",e)}static \u0275fac=(()=>{let e;return function(i){return(e||(e=ko(n)))(i||n)}})();static \u0275dir=qe({type:n,selectors:[["input","type","checkbox","formControlName","",3,"ngNoCva",""],["input","type","checkbox","formControl","",3,"ngNoCva",""],["input","type","checkbox","ngModel","",3,"ngNoCva",""]],hostBindings:function(o,i){o&1&&k("change",function(a){return i.onChange(a.target.checked)})("blur",function(){return i.onTouched()})},standalone:!1,features:[To([PE]),gn]})}return n})(),OE={provide:xs,useExisting:on(()=>zr),multi:!0};function vE(){let n=Kt()?Kt().getUserAgent():"";return/android (\d+)/.test(n.toLowerCase())}var yE=new B(""),zr=(()=>{class n extends Gb{_compositionMode;_composing=!1;constructor(e,o,i){super(e,o),this._compositionMode=i,this._compositionMode==null&&(this._compositionMode=!vE())}writeValue(e){let o=e??"";this.setProperty("value",o)}_handleInput(e){(!this._compositionMode||this._compositionMode&&!this._composing)&&this.onChange(e)}_compositionStart(){this._composing=!0}_compositionEnd(e){this._composing=!1,this._compositionMode&&this.onChange(e)}static \u0275fac=function(o){return new(o||n)(le(wt),le(Mt),le(yE,8))};static \u0275dir=qe({type:n,selectors:[["input","formControlName","",3,"type","checkbox",3,"ngNoCva",""],["textarea","formControlName","",3,"ngNoCva",""],["input","formControl","",3,"type","checkbox",3,"ngNoCva",""],["textarea","formControl","",3,"ngNoCva",""],["input","ngModel","",3,"type","checkbox",3,"ngNoCva",""],["textarea","ngModel","",3,"ngNoCva",""],["","ngDefaultControl",""]],hostBindings:function(o,i){o&1&&k("input",function(a){return i._handleInput(a.target.value)})("blur",function(){return i.onTouched()})("compositionstart",function(){return i._compositionStart()})("compositionend",function(a){return i._compositionEnd(a.target.value)})},standalone:!1,features:[To([OE]),gn]})}return n})();function tm(n){return n==null||nm(n)===0}function nm(n){return n==null?null:Array.isArray(n)||typeof n=="string"?n.length:n instanceof Set?n.size:null}var qb=new B(""),xE=new B(""),wE=/^(?=.{1,254}$)(?=.{1,64}@)[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/,Kf=class{static min(t){return SE(t)}static max(t){return kE(t)}static required(t){return Wb(t)}static requiredTrue(t){return EE(t)}static email(t){return DE(t)}static minLength(t){return IE(t)}static maxLength(t){return TE(t)}static pattern(t){return AE(t)}static nullValidator(t){return kd()}static compose(t){return Xb(t)}static composeAsync(t){return tP(t)}};function SE(n){return t=>{if(t.value==null||n==null)return null;let e=parseFloat(t.value);return!isNaN(e)&&e{if(t.value==null||n==null)return null;let e=parseFloat(t.value);return!isNaN(e)&&e>n?{max:{max:n,actual:t.value}}:null}}function Wb(n){return tm(n.value)?{required:!0}:null}function EE(n){return n.value===!0?null:{required:!0}}function DE(n){return tm(n.value)||wE.test(n.value)?null:{email:!0}}function IE(n){return t=>{let e=t.value?.length??nm(t.value);return e===null||e===0?null:e{let e=t.value?.length??nm(t.value);return e!==null&&e>n?{maxlength:{requiredLength:n,actualLength:e}}:null}}function AE(n){if(!n)return kd;let t,e;return typeof n=="string"?(e="",n.charAt(0)!=="^"&&(e+="^"),e+=n,n.charAt(n.length-1)!=="$"&&(e+="$"),t=new RegExp(e)):(e=n.toString(),t=n),o=>{if(tm(o.value))return null;let i=o.value;return t.test(i)?null:{pattern:{requiredPattern:e,actualValue:i}}}}function kd(n){return null}function Kb(n){return n!=null}function Yb(n){return Eo(n)?Fe(n):n}function Zb(n){let t={};return n.forEach(e=>{t=e!=null?S(S({},t),e):t}),Object.keys(t).length===0?null:t}function Qb(n,t){return t.map(e=>e(n))}function NE(n){return!n.validate}function Jb(n){return n.map(t=>NE(t)?t:e=>t.validate(e))}function Xb(n){if(!n)return null;let t=n.filter(Kb);return t.length==0?null:function(e){return Zb(Qb(e,t))}}function eP(n){return n!=null?Xb(Jb(n)):null}function tP(n){if(!n)return null;let t=n.filter(Kb);return t.length==0?null:function(e){let o=Qb(e,t).map(Yb);return bu(o).pipe(Pe(Zb))}}function nP(n){return n!=null?tP(Jb(n)):null}function Fb(n,t){return n===null?[t]:Array.isArray(n)?[...n,t]:[n,t]}function RE(n){return n._rawValidators}function FE(n){return n._rawAsyncValidators}function Yf(n){return n?Array.isArray(n)?n:[n]:[]}function Ed(n,t){return Array.isArray(n)?n.includes(t):n===t}function Lb(n,t){let e=Yf(t);return Yf(n).forEach(i=>{Ed(e,i)||e.push(i)}),e}function Vb(n,t){return Yf(t).filter(e=>!Ed(n,e))}var Dd=class{get value(){return this.control?this.control.value:null}get valid(){return this.control?this.control.valid:null}get invalid(){return this.control?this.control.invalid:null}get pending(){return this.control?this.control.pending:null}get disabled(){return this.control?this.control.disabled:null}get enabled(){return this.control?this.control.enabled:null}get errors(){return this.control?this.control.errors:null}get pristine(){return this.control?this.control.pristine:null}get dirty(){return this.control?this.control.dirty:null}get touched(){return this.control?this.control.touched:null}get status(){return this.control?this.control.status:null}get untouched(){return this.control?this.control.untouched:null}get statusChanges(){return this.control?this.control.statusChanges:null}get valueChanges(){return this.control?this.control.valueChanges:null}get path(){return null}_composedValidatorFn;_composedAsyncValidatorFn;_rawValidators=[];_rawAsyncValidators=[];_setValidators(t){this._rawValidators=t||[],this._composedValidatorFn=eP(this._rawValidators)}_setAsyncValidators(t){this._rawAsyncValidators=t||[],this._composedAsyncValidatorFn=nP(this._rawAsyncValidators)}get validator(){return this._composedValidatorFn||null}get asyncValidator(){return this._composedAsyncValidatorFn||null}_onDestroyCallbacks=[];_registerOnDestroy(t){this._onDestroyCallbacks.push(t)}_invokeOnDestroyCallbacks(){this._onDestroyCallbacks.forEach(t=>t()),this._onDestroyCallbacks=[]}reset(t=void 0){this.control?.reset(t)}hasError(t,e){return this.control?this.control.hasError(t,e):!1}getError(t,e){return this.control?this.control.getError(t,e):null}},Zf=class extends Dd{name;get formDirective(){return null}get path(){return null}};var bs="VALID",Sd="INVALID",Vr="PENDING",Ps="DISABLED",Ri=class{},Id=class extends Ri{value;source;constructor(t,e){super(),this.value=t,this.source=e}},Os=class extends Ri{pristine;source;constructor(t,e){super(),this.pristine=t,this.source=e}},vs=class extends Ri{touched;source;constructor(t,e){super(),this.touched=t,this.source=e}},jr=class extends Ri{status;source;constructor(t,e){super(),this.status=t,this.source=e}};var ys=class extends Ri{source;constructor(t){super(),this.source=t}};function LE(n){return(Ad(n)?n.validators:n)||null}function VE(n){return Array.isArray(n)?eP(n):n||null}function jE(n,t){return(Ad(t)?t.asyncValidators:n)||null}function $E(n){return Array.isArray(n)?nP(n):n||null}function Ad(n){return n!=null&&!Array.isArray(n)&&typeof n=="object"}var Qf=class{_pendingDirty=!1;_hasOwnPendingAsyncValidator=null;_pendingTouched=!1;_onCollectionChange=()=>{};_updateOn;_hasRequired=x(!1);_parent=null;_asyncValidationSubscription;_composedValidatorFn;_composedAsyncValidatorFn;_rawValidators;_rawAsyncValidators;value;constructor(t,e){this._assignValidators(t),this._assignAsyncValidators(e)}get validator(){return this._composedValidatorFn}set validator(t){this._rawValidators=this._composedValidatorFn=t,this._updateHasRequiredValidator()}get asyncValidator(){return this._composedAsyncValidatorFn}set asyncValidator(t){this._rawAsyncValidators=this._composedAsyncValidatorFn=t}get parent(){return this._parent}get status(){return de(this.statusReactive)}set status(t){de(()=>this.statusReactive.set(t))}_status=I(()=>this.statusReactive());statusReactive=x(void 0);get valid(){return this.status===bs}get invalid(){return this.status===Sd}get pending(){return this.status===Vr}get disabled(){return this.status===Ps}get enabled(){return this.status!==Ps}errors;get pristine(){return de(this.pristineReactive)}set pristine(t){de(()=>this.pristineReactive.set(t))}_pristine=I(()=>this.pristineReactive());pristineReactive=x(!0);get dirty(){return!this.pristine}get touched(){return de(this.touchedReactive)}set touched(t){de(()=>this.touchedReactive.set(t))}_touched=I(()=>this.touchedReactive());touchedReactive=x(!1);get untouched(){return!this.touched}_events=new ye;events=this._events.asObservable();valueChanges;statusChanges;get updateOn(){return this._updateOn?this._updateOn:this.parent?this.parent.updateOn:"change"}setValidators(t){this._assignValidators(t)}setAsyncValidators(t){this._assignAsyncValidators(t)}addValidators(t){this.setValidators(Lb(t,this._rawValidators))}addAsyncValidators(t){this.setAsyncValidators(Lb(t,this._rawAsyncValidators))}removeValidators(t){this.setValidators(Vb(t,this._rawValidators))}removeAsyncValidators(t){this.setAsyncValidators(Vb(t,this._rawAsyncValidators))}hasValidator(t){return Ed(this._rawValidators,t)}hasAsyncValidator(t){return Ed(this._rawAsyncValidators,t)}clearValidators(){this.validator=null}clearAsyncValidators(){this.asyncValidator=null}markAsTouched(t={}){let e=this.touched===!1;this.touched=!0;let o=t.sourceControl??this;t.onlySelf||this._parent?.markAsTouched(F(S({},t),{sourceControl:o})),e&&t.emitEvent!==!1&&this._events.next(new vs(!0,o))}markAllAsDirty(t={}){this.markAsDirty({onlySelf:!0,emitEvent:t.emitEvent,sourceControl:this}),this._forEachChild(e=>e.markAllAsDirty(t))}markAllAsTouched(t={}){this.markAsTouched({onlySelf:!0,emitEvent:t.emitEvent,sourceControl:this}),this._forEachChild(e=>e.markAllAsTouched(t))}markAsUntouched(t={}){let e=this.touched===!0;this.touched=!1,this._pendingTouched=!1;let o=t.sourceControl??this;this._forEachChild(i=>{i.markAsUntouched({onlySelf:!0,emitEvent:t.emitEvent,sourceControl:o})}),t.onlySelf||this._parent?._updateTouched(t,o),e&&t.emitEvent!==!1&&this._events.next(new vs(!1,o))}markAsDirty(t={}){let e=this.pristine===!0;this.pristine=!1;let o=t.sourceControl??this;t.onlySelf||this._parent?.markAsDirty(F(S({},t),{sourceControl:o})),e&&t.emitEvent!==!1&&this._events.next(new Os(!1,o))}markAsPristine(t={}){let e=this.pristine===!1;this.pristine=!0,this._pendingDirty=!1;let o=t.sourceControl??this;this._forEachChild(i=>{i.markAsPristine({onlySelf:!0,emitEvent:t.emitEvent})}),t.onlySelf||this._parent?._updatePristine(t,o),e&&t.emitEvent!==!1&&this._events.next(new Os(!0,o))}markAsPending(t={}){this.status=Vr;let e=t.sourceControl??this;t.emitEvent!==!1&&(this._events.next(new jr(this.status,e)),this.statusChanges.emit(this.status)),t.onlySelf||this._parent?.markAsPending(F(S({},t),{sourceControl:e}))}disable(t={}){let e=this._parentMarkedDirty(t.onlySelf);this.status=Ps,this.errors=null,this._forEachChild(i=>{i.disable(F(S({},t),{onlySelf:!0}))}),this._updateValue();let o=t.sourceControl??this;t.emitEvent!==!1&&(this._events.next(new Id(this.value,o)),this._events.next(new jr(this.status,o)),this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),this._updateAncestors(F(S({},t),{skipPristineCheck:e}),this),this._onDisabledChange.forEach(i=>i(!0))}enable(t={}){let e=this._parentMarkedDirty(t.onlySelf);this.status=bs,this._forEachChild(o=>{o.enable(F(S({},t),{onlySelf:!0}))}),this.updateValueAndValidity({onlySelf:!0,emitEvent:t.emitEvent}),this._updateAncestors(F(S({},t),{skipPristineCheck:e}),this),this._onDisabledChange.forEach(o=>o(!1))}_updateAncestors(t,e){t.onlySelf||(this._parent?.updateValueAndValidity(t),t.skipPristineCheck||this._parent?._updatePristine({},e),this._parent?._updateTouched({},e))}setParent(t){this._parent=t}getRawValue(){return this.value}updateValueAndValidity(t={}){if(this._setInitialStatus(),this._updateValue(),this.enabled){let o=this._cancelExistingSubscription();this.errors=this._runValidator(),this.status=this._calculateStatus(),(this.status===bs||this.status===Vr)&&this._runAsyncValidator(o,t.emitEvent)}let e=t.sourceControl??this;t.emitEvent!==!1&&(this._events.next(new Id(this.value,e)),this._events.next(new jr(this.status,e)),this.valueChanges.emit(this.value),this.statusChanges.emit(this.status)),t.onlySelf||this._parent?.updateValueAndValidity(F(S({},t),{sourceControl:e}))}_updateTreeValidity(t={emitEvent:!0}){this._forEachChild(e=>e._updateTreeValidity(t)),this.updateValueAndValidity({onlySelf:!0,emitEvent:t.emitEvent})}_setInitialStatus(){this.status=this._allControlsDisabled()?Ps:bs}_runValidator(){return this.validator?this.validator(this):null}_runAsyncValidator(t,e){if(this.asyncValidator){this.status=Vr,this._hasOwnPendingAsyncValidator={emitEvent:e!==!1,shouldHaveEmitted:t!==!1};let o=Yb(this.asyncValidator(this));this._asyncValidationSubscription=o.subscribe(i=>{this._hasOwnPendingAsyncValidator=null,this.setErrors(i,{emitEvent:e,shouldHaveEmitted:t})})}}_cancelExistingSubscription(){if(this._asyncValidationSubscription){this._asyncValidationSubscription.unsubscribe();let t=(this._hasOwnPendingAsyncValidator?.emitEvent||this._hasOwnPendingAsyncValidator?.shouldHaveEmitted)??!1;return this._hasOwnPendingAsyncValidator=null,t}return!1}setErrors(t,e={}){this.errors=t,this._updateControlsErrors(e.emitEvent!==!1,this,e.shouldHaveEmitted)}get(t){let e=t;return e==null||(Array.isArray(e)||(e=e.split(".")),e.length===0)?null:e.reduce((o,i)=>o&&o._find(i),this)}getError(t,e){let o=e?this.get(e):this;return o?.errors?o.errors[t]:null}hasError(t,e){return!!this.getError(t,e)}get root(){let t=this;for(;t._parent;)t=t._parent;return t}_updateControlsErrors(t,e,o){this.status=this._calculateStatus(),t&&this.statusChanges.emit(this.status),(t||o)&&this._events.next(new jr(this.status,e)),this._parent&&this._parent._updateControlsErrors(t,e,o)}_initObservables(){this.valueChanges=new Ke,this.statusChanges=new Ke}_calculateStatus(){return this._allControlsDisabled()?Ps:this.errors?Sd:this._hasOwnPendingAsyncValidator||this._anyControlsHaveStatus(Vr)?Vr:this._anyControlsHaveStatus(Sd)?Sd:bs}_anyControlsHaveStatus(t){return this._anyControls(e=>e.status===t)}_anyControlsDirty(){return this._anyControls(t=>t.dirty)}_anyControlsTouched(){return this._anyControls(t=>t.touched)}_updatePristine(t,e){let o=!this._anyControlsDirty(),i=this.pristine!==o;this.pristine=o,t.onlySelf||this._parent?._updatePristine(t,e),i&&this._events.next(new Os(this.pristine,e))}_updateTouched(t={},e){this.touched=this._anyControlsTouched(),this._events.next(new vs(this.touched,e)),t.onlySelf||this._parent?._updateTouched(t,e)}_onDisabledChange=[];_registerOnCollectionChange(t){this._onCollectionChange=t}_setUpdateStrategy(t){Ad(t)&&t.updateOn!=null&&(this._updateOn=t.updateOn)}_parentMarkedDirty(t){return!t&&!!this._parent?.dirty&&!this._parent._anyControlsDirty()}_find(t){return null}_assignValidators(t){this._rawValidators=Array.isArray(t)?t.slice():t,this._composedValidatorFn=VE(this._rawValidators),this._updateHasRequiredValidator()}_assignAsyncValidators(t){this._rawAsyncValidators=Array.isArray(t)?t.slice():t,this._composedAsyncValidatorFn=$E(this._rawAsyncValidators)}_updateHasRequiredValidator(){de(()=>this._hasRequired.set(this.hasValidator(Kf.required)))}};function zE(n){return n.tagName==="INPUT"||n.tagName==="SELECT"||n.tagName==="TEXTAREA"}function BE(n,t,e,o){switch(e){case"name":n.setAttribute(t,e,o);break;case"disabled":case"readonly":case"required":o?n.setAttribute(t,e,""):n.removeAttribute(t,e);break;case"max":case"min":case"minLength":case"maxLength":o!==void 0?n.setAttribute(t,e,o.toString()):n.removeAttribute(t,e);break}}var Jf=class{kind;context;control;message;constructor({kind:t,context:e,control:o}){this.kind=t,this.context=e,this.control=o}};var HE=(()=>{class n{_validator=kd;_onChange;_enabled;ngOnChanges(e){if(this.inputName in e){let o=this.normalizeInput(e[this.inputName].currentValue);this._enabled=this.enabled(o),this._validator=this._enabled?this.createValidator(o):kd,this._onChange?.()}}validate(e){return this._validator(e)}registerOnValidatorChange(e){this._onChange=e}enabled(e){return e!=null}static \u0275fac=function(o){return new(o||n)};static \u0275dir=qe({type:n,features:[In]})}return n})();var UE={provide:qb,useExisting:on(()=>oP),multi:!0};var oP=(()=>{class n extends HE{required;inputName="required";normalizeInput=Si;createValidator=e=>Wb;enabled(e){return e}static \u0275fac=(()=>{let e;return function(i){return(e||(e=ko(n)))(i||n)}})();static \u0275dir=qe({type:n,selectors:[["","required","","formControlName","",3,"type","checkbox"],["","required","","formControl","",3,"type","checkbox"],["","required","","ngModel","",3,"type","checkbox"]],hostVars:1,hostBindings:function(o,i){o&2&&Wt("required",i._enabled?"":null)},inputs:{required:"required"},standalone:!1,features:[To([UE]),gn]})}return n})();var GE=new B(""),iP=new B("",{factory:()=>om}),om="always";function qE(n,t){return[...t.path,n]}function jb(n,t,e=om){KE(n,t),t.valueAccessor.writeValue(n.value),(n.disabled||e==="always")&&t.valueAccessor.setDisabledState?.(n.disabled),YE(n,t),QE(n,t),ZE(n,t),WE(n,t)}function $b(n,t){n.forEach(e=>{e.registerOnValidatorChange&&e.registerOnValidatorChange(t)})}function WE(n,t){if(t.valueAccessor.setDisabledState){let e=o=>{t.valueAccessor.setDisabledState(o)};n.registerOnDisabledChange(e),t._registerOnDestroy(()=>{n._unregisterOnDisabledChange(e)})}}function KE(n,t){let e=RE(n);t.validator!==null?n.setValidators(Fb(e,t.validator)):typeof e=="function"&&n.setValidators([e]);let o=FE(n);t.asyncValidator!==null?n.setAsyncValidators(Fb(o,t.asyncValidator)):typeof o=="function"&&n.setAsyncValidators([o]);let i=()=>n.updateValueAndValidity();$b(t._rawValidators,i),$b(t._rawAsyncValidators,i)}function YE(n,t){t.valueAccessor.registerOnChange(e=>{n._pendingValue=e,n._pendingChange=!0,n._pendingDirty=!0,n.updateOn==="change"&&rP(n,t)})}function ZE(n,t){t.valueAccessor.registerOnTouched(()=>{n._pendingTouched=!0,n.updateOn==="blur"&&n._pendingChange&&rP(n,t),n.updateOn!=="submit"&&n.markAsTouched()})}function rP(n,t){n._pendingDirty&&n.markAsDirty(),n.setValue(n._pendingValue,{emitModelToViewChange:!1}),t.viewToModelUpdate(n._pendingValue),n._pendingChange=!1}function QE(n,t){let e=(o,i)=>{t.valueAccessor.writeValue(o),i&&t.viewToModelUpdate(o)};n.registerOnChange(e),t._registerOnDestroy(()=>{n._unregisterOnChange(e)})}function JE(n,t){if(!Object.hasOwn(n,"model"))return!1;let e=n.model;return e.isFirstChange()?!0:!Object.is(t,e.currentValue)}function XE(n){return Object.getPrototypeOf(n.constructor)===Td}function eD(n,t){if(!t)return null;Array.isArray(t);let e,o,i;return t.forEach(r=>{r.constructor===zr?e=r:XE(r)?o=r:i=r}),i||o||e||null}var tD={provide:GE,useFactory:()=>{let n=D($r,{self:!0});return{setParseErrors:t=>{n.setParseErrorSource(t)},set onReset(t){n.onReset=t}}}},$r=class extends Dd{_parent=null;name=null;valueAccessor=null;isCustomControlBased=!1;userOnReset;resetSubscription;set onReset(t){this.userOnReset=t,this.resetSubscription?.unsubscribe(),this.resetSubscription=void 0,this.control&&(this.resetSubscription=this.control.events.subscribe(e=>{e instanceof ys&&this.control&&this.userOnReset?.(this.control.value)}),this.subscription?.add(this.resetSubscription))}isNativeFormElement=!1;rawValueAccessors;_selectedValueAccessor=null;get selectedValueAccessor(){return this._selectedValueAccessor??=eD(this,this.rawValueAccessors)}parseErrorsValidator=null;renderer;injector;requiredValidatorViaDi;subscription;customControlBindings=null;constructor(t,e,o){super(),this.injector=t,this.renderer=e,this.rawValueAccessors=o,this.injector?.get(Ae)?.onDestroy(()=>{this.removeParseErrorsValidator(this.control),this.subscription?.unsubscribe()})}setupCustomControl(){this.subscription?.unsubscribe();let t=this.injector?.get(oo);if(!this.control||!t)return;let e=t.markForCheck.bind(t);this.subscription=new Be,this.subscription.add(this.control.valueChanges.subscribe(e)),this.subscription.add(this.control.statusChanges.subscribe(e)),this.resetSubscription?.unsubscribe(),this.resetSubscription=void 0,this.userOnReset&&(this.resetSubscription=this.control.events.subscribe(o=>{o instanceof ys&&this.control&&this.userOnReset?.(this.control.value)}),this.subscription.add(this.resetSubscription)),this.parseErrorsValidator&&this.control.addValidators(this.parseErrorsValidator)}ngControlCreate(t){!t.nativeElement.hasAttribute?.("ngNoCva")&&(this.rawValueAccessors&&this.rawValueAccessors.length>0||this.valueAccessor!==null)||!t.customControl||(this.isCustomControlBased=!0,t.listenToCustomControlModel(i=>{this.control?.setValue(i,{emitModelToViewChange:!1}),this.control?.markAsDirty(),this.viewToModelUpdate(i)}),t.listenToCustomControlOutput("touch",()=>{this.control?.markAsTouched()}),this.customControlBindings={},this.isNativeFormElement=zE(t.nativeElement),this.requiredValidatorViaDi=this._rawValidators.find(i=>i instanceof oP))}ngControlUpdate(t,e){if(!this.isCustomControlBased)return;let o=this.control,i=this.customControlBindings;Object.is(i.value,o.value)||(i.value=o.value,t.setCustomControlModelInput(o.value)),this.bindControlProperty(t,i,"touched",o.touched),this.bindControlProperty(t,i,"dirty",o.dirty),this.bindControlProperty(t,i,"valid",o.valid),this.bindControlProperty(t,i,"invalid",o.invalid),this.bindControlProperty(t,i,"pending",o.pending),this.bindControlProperty(t,i,"disabled",o.disabled),this.shouldBindRequired&&this.bindControlProperty(t,i,"required",this.isRequired);let r=o.errors;if(i.errors!==r){i.errors=r;let a=this._convertErrors(r);t.setInputOnDirectives("errors",a)}}get isRequired(){return(this.requiredValidatorViaDi?._enabled||this.control?._hasRequired())??!1}get shouldBindRequired(){return!0}bindControlProperty(t,e,o,i){if(e[o]===i)return;e[o]=i;let r=t.setInputOnDirectives(o,i);this.isNativeFormElement&&!r&&(o==="disabled"||o==="required")&&this.renderer&&BE(this.renderer,t.nativeElement,o,i)}_convertErrors(t){if(t===null)return[];let e=this.control;return Object.entries(t).map(([o,i])=>new Jf({context:i,kind:o,control:e}))}setParseErrorSource(t){if(t===void 0)return;let e=null,o=I(()=>{let i=t();return i.length===0?null:i.reduce((r,a)=>(r[a.kind]=a,r),{})});this.parseErrorsValidator=(()=>e).bind(this),Ye(()=>{e=o(),this.control?.updateValueAndValidity({emitEvent:!1})},{injector:this.injector})}removeParseErrorsValidator(t){this.parseErrorsValidator&&(t?.removeValidators(this.parseErrorsValidator),t?.updateValueAndValidity({emitEvent:!1}))}},Xf=class{_cd;constructor(t){this._cd=t}get isTouched(){return this._cd?.control?._touched?.(),!!this._cd?.control?.touched}get isUntouched(){return!!this._cd?.control?.untouched}get isPristine(){return this._cd?.control?._pristine?.(),!!this._cd?.control?.pristine}get isDirty(){return!!this._cd?.control?.dirty}get isValid(){return this._cd?.control?._status?.(),!!this._cd?.control?.valid}get isInvalid(){return!!this._cd?.control?.invalid}get isPending(){return!!this._cd?.control?.pending}get isSubmitted(){return this._cd?._submitted?.(),!!this._cd?.submitted}};var Nd=(()=>{class n extends Xf{constructor(e){super(e)}static \u0275fac=function(o){return new(o||n)(le($r,2))};static \u0275dir=qe({type:n,selectors:[["","formControlName",""],["","ngModel",""],["","formControl",""]],hostVars:14,hostBindings:function(o,i){o&2&&w("ng-untouched",i.isUntouched)("ng-touched",i.isTouched)("ng-pristine",i.isPristine)("ng-dirty",i.isDirty)("ng-valid",i.isValid)("ng-invalid",i.isInvalid)("ng-pending",i.isPending)},standalone:!1,features:[gn]})}return n})();function zb(n,t){let e=n.indexOf(t);e>-1&&n.splice(e,1)}function Bb(n){return typeof n=="object"&&n!==null&&Object.keys(n).length===2&&"value"in n&&"disabled"in n}var nD=class extends Qf{defaultValue=null;_onChange=[];_pendingValue;_pendingChange=!1;constructor(t=null,e,o){super(LE(e),jE(o,e)),this._applyFormState(t),this._setUpdateStrategy(e),this._initObservables(),this.updateValueAndValidity({onlySelf:!0,emitEvent:!!this.asyncValidator}),Ad(e)&&(e.nonNullable||e.initialValueIsDefault)&&(Bb(t)?this.defaultValue=t.value:this.defaultValue=t)}setValue(t,e={}){de(()=>{this.value=this._pendingValue=t,this._onChange.length&&e.emitModelToViewChange!==!1&&this._onChange.forEach(o=>o(this.value,e.emitViewToModelChange!==!1)),this.updateValueAndValidity(e)})}patchValue(t,e={}){this.setValue(t,e)}reset(t=this.defaultValue,e={}){this._applyFormState(t),this.markAsPristine(e),this.markAsUntouched(e),this.setValue(this.value,e),e.overwriteDefaultValue&&(this.defaultValue=this.value),this._pendingChange=!1,e?.emitEvent!==!1&&this._events.next(new ys(this))}_updateValue(){}_anyControls(t){return!1}_allControlsDisabled(){return this.disabled}registerOnChange(t){this._onChange.push(t)}_unregisterOnChange(t){zb(this._onChange,t)}registerOnDisabledChange(t){this._onDisabledChange.push(t)}_unregisterOnDisabledChange(t){zb(this._onDisabledChange,t)}_forEachChild(t){}_syncPendingControls(){return this.updateOn==="submit"&&(this._pendingDirty&&this.markAsDirty(),this._pendingTouched&&this.markAsTouched(),this._pendingChange)?(this.setValue(this._pendingValue,{onlySelf:!0,emitModelToViewChange:!1}),!0):!1}_applyFormState(t){Bb(t)?(this.value=this._pendingValue=t.value,t.disabled?this.disable({onlySelf:!0,emitEvent:!1}):this.enable({onlySelf:!0,emitEvent:!1})):this.value=this._pendingValue=t}};var oD={provide:$r,useExisting:on(()=>ws)},Hb=Promise.resolve(),ws=(()=>{class n extends $r{_changeDetectorRef;callSetDisabledState;control=new nD;static ngAcceptInputType_isDisabled;_registered=!1;_ngModelInjector;viewModel;name="";isDisabled;model;options;update=new Ke;constructor(e,o,i,r,a,s,c,p){super(c,p,r),this._changeDetectorRef=a,this.callSetDisabledState=s,this._parent=e,this._setValidators(o),this._setAsyncValidators(i)}ngOnChanges(e){if(this._registered,this._checkForErrors(),!this._registered||"name"in e){if(this._registered&&(this._checkName(),this.formDirective)){let o=e.name.previousValue;this.formDirective.removeControl({name:o,path:this._getPath(o)})}this._setUpControl()}"isDisabled"in e&&this._updateDisabled(e),JE(e,this.viewModel)&&(this._updateValue(this.model),this.viewModel=this.model)}ngOnDestroy(){this.formDirective?.removeControl(this)}\u0275ngControlCreate(e){super.ngControlCreate(e)}\u0275ngControlUpdate(e){super.ngControlUpdate(e,!1)}get shouldBindRequired(){return!1}get path(){return this._getPath(this.name)}get formDirective(){return this._parent?this._parent.formDirective:null}viewToModelUpdate(e){this.viewModel=e,this.update.emit(e)}_setUpControl(){this._setUpdateStrategy(),this._isStandalone()?this._setUpStandalone():this.formDirective.addControl(this),this._registered=!0}_setUpdateStrategy(){this.options&&this.options.updateOn!=null&&(this.control._updateOn=this.options.updateOn)}_isStandalone(){return!this._parent||!!(this.options&&this.options.standalone)}_setUpStandalone(){this.isCustomControlBased?this.setupCustomControl():(this.valueAccessor??=this.selectedValueAccessor,jb(this.control,this,this.callSetDisabledState)),this.control.updateValueAndValidity({emitEvent:!1})}_setupWithForm(e){this.isCustomControlBased?this.setupCustomControl():(this.valueAccessor??=this.selectedValueAccessor,jb(this.control,this,e))}_checkForErrors(){this._checkName()}_checkName(){this.options&&this.options.name&&(this.name=this.options.name),!this._isStandalone()&&this.name}_updateValue(e){Hb.then(()=>{this.control.setValue(e,{emitViewToModelChange:!1}),this._changeDetectorRef?.markForCheck()})}_updateDisabled(e){let o=e.isDisabled.currentValue,i=o!==0&&Si(o);Hb.then(()=>{i&&!this.control.disabled?this.control.disable():!i&&this.control.disabled&&this.control.enable(),this._changeDetectorRef?.markForCheck()})}_getPath(e){return this._parent?qE(e,this._parent):[e]}static \u0275fac=function(o){return new(o||n)(le(Zf,9),le(qb,10),le(xE,10),le(xs,10),le(oo,8),le(iP,8),le(Ct,8),le(wt,8))};static \u0275dir=qe({type:n,selectors:[["","ngModel","",3,"formControlName","",3,"formControl",""]],inputs:{name:"name",isDisabled:[0,"disabled","isDisabled"],model:[0,"ngModel","model"],options:[0,"ngModelOptions","options"]},outputs:{update:"ngModelChange"},exportAs:["ngModel"],standalone:!1,features:[To([oD,tD]),gn,In,jp(null)]})}return n})();var iD={provide:xs,useExisting:on(()=>sP),multi:!0};function aP(n,t){return n==null?`${t}`:(t&&typeof t=="object"&&(t="Object"),`${n}: ${t}`.slice(0,50))}function rD(n){return n.split(":")[0]}var sP=(()=>{class n extends Td{value;_optionMap=new Map;_idCounter=0;set compareWith(e){this._compareWith=e}_compareWith=Object.is;appRefInjector=D(Xn).injector;destroyRef=D(Ae);cdr=D(oo);_queuedWrite=!1;_writeValueAfterRender(){this._queuedWrite||this.appRefInjector.destroyed||(this._queuedWrite=!0,La({write:()=>{this.destroyRef.destroyed||(this._queuedWrite=!1,this.writeValue(this.value))}},{injector:this.appRefInjector}))}writeValue(e){this.cdr.markForCheck(),this.value=e;let o=this._getOptionId(e),i=aP(o,e);this.setProperty("value",i)}registerOnChange(e){this.onChange=o=>{this.value=this._getOptionValue(o),e(this.value)}}_registerOption(){return(this._idCounter++).toString()}_getOptionId(e){for(let o of this._optionMap.keys())if(this._compareWith(this._optionMap.get(o),e))return o;return null}_getOptionValue(e){let o=rD(e);return this._optionMap.has(o)?this._optionMap.get(o):e}static \u0275fac=(()=>{let e;return function(i){return(e||(e=ko(n)))(i||n)}})();static \u0275dir=qe({type:n,selectors:[["select","formControlName","",3,"multiple","",3,"ngNoCva",""],["select","formControl","",3,"multiple","",3,"ngNoCva",""],["select","ngModel","",3,"multiple","",3,"ngNoCva",""]],hostBindings:function(o,i){o&1&&k("change",function(a){return i.onChange(a.target.value)})("blur",function(){return i.onTouched()})},inputs:{compareWith:"compareWith"},standalone:!1,features:[To([iD]),gn]})}return n})(),cP=(()=>{class n{_element;_renderer;_select;id;constructor(e,o,i){this._element=e,this._renderer=o,this._select=i,this._select&&(this.id=this._select._registerOption())}set ngValue(e){this._select!=null&&(this._select._optionMap.set(this.id,e),this._setElementValue(aP(this.id,e)),this._select._writeValueAfterRender())}set value(e){this._setElementValue(e),this._select?._writeValueAfterRender()}_setElementValue(e){this._renderer.setProperty(this._element.nativeElement,"value",e)}ngOnDestroy(){this._select?._optionMap.delete(this.id),this._select?._writeValueAfterRender()}static \u0275fac=function(o){return new(o||n)(le(Mt),le(wt),le(sP,9))};static \u0275dir=qe({type:n,selectors:[["option"]],inputs:{ngValue:"ngValue",value:"value"},standalone:!1})}return n})(),aD={provide:xs,useExisting:on(()=>lP),multi:!0};function Ub(n,t){return n==null?`${t}`:(typeof t=="string"&&(t=`'${t}'`),t&&typeof t=="object"&&(t="Object"),`${n}: ${t}`.slice(0,50))}function sD(n){return n.split(":")[0]}var lP=(()=>{class n extends Td{value;_optionMap=new Map;_idCounter=0;set compareWith(e){this._compareWith=e}_compareWith=Object.is;writeValue(e){this.value=e;let o;if(Array.isArray(e)){let i=e.map(r=>this._getOptionId(r));o=(r,a)=>{r._setSelected(i.indexOf(a)>-1)}}else o=i=>{i._setSelected(!1)};this._optionMap.forEach(o)}registerOnChange(e){this.onChange=o=>{let i=[],r=o.selectedOptions;if(r!==void 0){let a=r;for(let s=0;s{let e;return function(i){return(e||(e=ko(n)))(i||n)}})();static \u0275dir=qe({type:n,selectors:[["select","multiple","","formControlName","",3,"ngNoCva",""],["select","multiple","","formControl","",3,"ngNoCva",""],["select","multiple","","ngModel","",3,"ngNoCva",""]],hostBindings:function(o,i){o&1&&k("change",function(a){return i.onChange(a.target)})("blur",function(){return i.onTouched()})},inputs:{compareWith:"compareWith"},standalone:!1,features:[To([aD]),gn]})}return n})(),dP=(()=>{class n{_element;_renderer;_select;id;_value;constructor(e,o,i){this._element=e,this._renderer=o,this._select=i,this._select&&(this.id=this._select._registerOption(this))}set ngValue(e){this._select!=null&&(this._value=e,this._setElementValue(Ub(this.id,e)),this._select.writeValue(this._select.value))}set value(e){this._select?(this._value=e,this._setElementValue(Ub(this.id,e)),this._select.writeValue(this._select.value)):this._setElementValue(e)}_setElementValue(e){this._renderer.setProperty(this._element.nativeElement,"value",e)}_setSelected(e){this._renderer.setProperty(this._element.nativeElement,"selected",e)}ngOnDestroy(){this._select&&(this._select._optionMap.delete(this.id),this._select.writeValue(this._select.value))}static \u0275fac=function(o){return new(o||n)(le(Mt),le(wt),le(lP,9))};static \u0275dir=qe({type:n,selectors:[["option"]],inputs:{ngValue:"ngValue",value:"value"},standalone:!1})}return n})();var cD=(()=>{class n{static \u0275fac=function(o){return new(o||n)};static \u0275mod=Jn({type:n});static \u0275inj=Pn({})}return n})();var Qt=(()=>{class n{static withConfig(e){return{ngModule:n,providers:[{provide:iP,useValue:e.callSetDisabledState??om}]}}static \u0275fac=function(o){return new(o||n)};static \u0275mod=Jn({type:n});static \u0275inj=Pn({imports:[cD]})}return n})();function oe(n){if(n==null)return null;if(Array.isArray(n)){if(n.length===4&&n.every(e=>typeof e=="number"&&!isNaN(e))||n.length===2&&n.every(e=>typeof e=="number"&&!isNaN(e)))return n;if(n.length===2&&Array.isArray(n[0])&&Array.isArray(n[1])){let e=oe(n[0]),o=oe(n[1]);if(e&&o&&e.length===2&&o.length===2)return[e[0],e[1],o[0],o[1]]}let t=[];for(let e of n)if(typeof e=="number"&&!isNaN(e))t.push(e);else if(typeof e=="string"){let o=e.match(/-?\d+(?:\.\d+)?/g);o&&o.forEach(i=>t.push(Number(i)))}else if(Array.isArray(e)){let o=oe(e);o&&t.push(...o)}if(t.length===4||t.length===2)return t}if(typeof n=="string"){let t=n.trim();if(!t)return null;try{let o=JSON.parse(t),i=oe(o);if(i)return i}catch(o){}let e=t.match(/-?\d+(?:\.\d+)?/g);if(e&&(e.length===4||e.length===2))return e.map(o=>Number(o))}return null}function Br(n){if(!n)return null;if(typeof n=="string"){let t=n.trim();try{let r=JSON.parse(t),a=Br(r);if(a&&a.length>0)return a}catch(r){}let e=[],o=/\[\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\]/g,i;for(;(i=o.exec(t))!==null;)e.push([Number(i[1]),Number(i[2])]);if(e.length>0)return e}if(Array.isArray(n)){let t=[];for(let e of n){let o=oe(e);o&&o.length===2?t.push([o[0],o[1]]):Array.isArray(e)&&e.length>=2&&typeof e[0]=="number"&&typeof e[1]=="number"&&t.push([e[0],e[1]])}if(t.length>0)return t}return null}function Pt(n){if(typeof n!="string")return!1;let t=n.trim().toLowerCase();return/\d/.test(t)?!1:["up","down","left","right","swipe up","swipe down","swipe left","swipe right","scroll up","scroll down","scroll left","scroll right","top","bottom"].includes(t)}function Rd(n,t,e){if(!n||!t||!e)return;t.innerHTML="";let o=Array.isArray(e)?e:[e],i=n.naturalWidth||1080,r=n.naturalHeight||2400;o.forEach(a=>{if(!a||typeof a!="object")return;let s=(a.action||a.name||"tap").toLowerCase(),c=a.payload&&typeof a.payload=="object"?a.payload:{},p=c.args&&typeof c.args=="object"?c.args:a.args||{};if(["manage_app","launch_app","open_app","stop_app","close_app","press_key","press_home","press_back","wait_for_delay","wait","delay","report_task_status","report_failure_analysis"].includes(s))return;let _=s.includes("sequence")||!!(a.sequence||p.sequence||a.normalized_sequence||p.normalized_sequence),C=a.normalized_sequence||p.normalized_sequence||a.sequence||p.sequence||a.targets||p.targets||(Array.isArray(a.coordinates)&&Array.isArray(a.coordinates[0])?a.coordinates:null)||(Array.isArray(p.coordinates)&&Array.isArray(p.coordinates[0])?p.coordinates:null),h=Br(C);if(h&&h.length>0&&(_||h.length>1)){let X=!!(a.normalized_sequence||p.normalized_sequence)||h.every(W=>W[0]<=1e3&&W[1]<=1e3);for(let W=0;W{rm(W[0],W[1],s,i,r,t,X,Me+1,h.length)});return}let P=s.includes("swipe")||s.includes("scroll")||s.includes("drag")||s.includes("slide"),T=oe(a.normalized_start_coordinates||p.normalized_start_coordinates),A=oe(a.normalized_end_coordinates||p.normalized_end_coordinates),N=T||oe(a.start_coordinates||a.start_point||a.start||a.from||p.start_coordinates||p.start_point||p.start||p.from),E=A||oe(a.end_coordinates||a.end_point||a.end||a.to||p.end_coordinates||p.end_point||p.end||p.to);if(N&&E&&N.length>=2&&E.length>=2){am(N[0],N[1],E[0],E[1],i,r,t,!!(T&&A),s);return}let U=oe(a.normalized_coordinates||p.normalized_coordinates),ne=!!U,J=U||oe(a.coordinates)||oe(p.coordinates)||oe(a.coords)||oe(p.coords)||oe(a.target)||oe(p.target)||(P?oe(p.action):null)||(P?oe(a.action):null)||(P?oe(p.gesture):null)||(P?oe(a.gesture):null);if(J&&Array.isArray(J)){if(J.length===4){am(J[0],J[1],J[2],J[3],i,r,t,ne,s);return}else if(J.length===2&&!P){rm(J[0],J[1],s,i,r,t,ne);return}}if(P){let X=(typeof p.action=="string"&&Pt(p.action)?p.action:null)||(typeof a.action=="string"&&a.action!==s&&Pt(a.action)?a.action:null)||(Pt(p.direction)?p.direction:null)||(Pt(a.direction)?a.direction:null)||(Pt(p.gesture)?p.gesture:null)||(Pt(a.gesture)?a.gesture:null)||"",W=String(X).toLowerCase().trim();if(W){let Me=500,Ee=750,ft=500,Jt=250,Xt="SWIPE";W.includes("up")||W.includes("top")||s.includes("up")?(Me=500,Ee=750,ft=500,Jt=250,Xt="SWIPE UP"):W.includes("down")||W.includes("bottom")||s.includes("down")?(Me=500,Ee=250,ft=500,Jt=750,Xt="SWIPE DOWN"):W.includes("left")||s.includes("left")?(Me=800,Ee=500,ft=200,Jt=500,Xt="SWIPE LEFT"):(W.includes("right")||s.includes("right"))&&(Me=200,Ee=500,ft=800,Jt=500,Xt="SWIPE RIGHT"),am(Me,Ee,ft,Jt,i,r,t,!0,Xt);return}}J&&Array.isArray(J)&&J.length===2&&rm(J[0],J[1],s,i,r,t,ne)})}function rm(n,t,e,o,i,r,a=!1,s,c){let p,f;a||!a&&n<=1e3&&t<=1e3?(p=n/10,f=t/10):(p=n/(o||1080)*100,f=t/(i||2400)*100);let _=Math.max(1.5,Math.min(98.5,p)),C=Math.max(1.5,Math.min(98.5,f)),h=document.createElement("div");h.className="action-point-marker",h.style.position="absolute",h.style.left=`${_}%`,h.style.top=`${C}%`,h.style.transform="translate(-50%, -50%)",h.style.pointerEvents="none",h.style.zIndex="10";let P=s!==void 0?20:14,T=document.createElement("div");T.style.width=`${P}px`,T.style.height=`${P}px`,T.style.borderRadius="50%",T.style.backgroundColor="#ef4444",T.style.border="2px solid white",T.style.boxShadow="0 0 6px rgba(0,0,0,0.5)",s!==void 0&&(T.style.display="flex",T.style.alignItems="center",T.style.justifyContent="center",T.style.fontSize="11px",T.style.fontWeight="bold",T.style.color="white",T.innerText=String(s));let A=P*2,N=document.createElement("div");N.style.position="absolute",N.style.top=`-${P/2+2}px`,N.style.left=`-${P/2+2}px`,N.style.width=`${A}px`,N.style.height=`${A}px`,N.style.borderRadius="50%",N.style.border="2px solid #ef4444",N.style.animation="pulse-animation 1.5s infinite ease-out",N.style.pointerEvents="none";let E=document.createElement("div");s!==void 0?E.innerText=c&&c>1?`#${s} TAP`:e.toUpperCase():E.innerText=e.toUpperCase(),E.style.position="absolute",E.style.backgroundColor="rgba(15, 23, 42, 0.9)",E.style.color="white",E.style.padding="1.5px 5px",E.style.borderRadius="3px",E.style.fontSize="9px",E.style.fontWeight="bold",E.style.letterSpacing="0.5px",E.style.whiteSpace="nowrap",E.style.boxShadow="0 1px 4px rgba(0,0,0,0.4)",E.style.border="1px solid rgba(255,255,255,0.2)",C>85?(E.style.bottom=`${P+2}px`,E.style.top="auto"):(E.style.top=`${P+2}px`,E.style.bottom="auto"),_>82?(E.style.right="0px",E.style.left="auto",E.style.transform="none"):_<18?(E.style.left="0px",E.style.right="auto",E.style.transform="none"):(E.style.left="50%",E.style.right="auto",E.style.transform="translateX(-50%)"),h.appendChild(N),h.appendChild(T),h.appendChild(E),r.appendChild(h)}function lD(n,t,e,o,i,r,a,s=!1){let c=s||n<=1e3&&t<=1e3&&e<=1e3&&o<=1e3,p=c?n/10:n/(i||1080)*100,f=c?t/10:t/(r||2400)*100,_=c?e/10:e/(i||1080)*100,C=c?o/10:o/(r||2400)*100,h=Math.max(1.5,Math.min(98.5,p)),P=Math.max(1.5,Math.min(98.5,f)),T=Math.max(1.5,Math.min(98.5,_)),A=Math.max(1.5,Math.min(98.5,C)),N=a.querySelector("svg");N||(N=document.createElementNS("http://www.w3.org/2000/svg","svg"),N.setAttribute("width","100%"),N.setAttribute("height","100%"),N.style.position="absolute",N.style.top="0",N.style.left="0",N.style.pointerEvents="none",N.style.overflow="visible",N.style.zIndex="5",a.appendChild(N));let E=N.querySelector("defs");E||(E=document.createElementNS("http://www.w3.org/2000/svg","defs"),N.insertBefore(E,N.firstChild));let U="seq-arrow-"+Math.random().toString(36).substring(2,9),ne=document.createElementNS("http://www.w3.org/2000/svg","marker");ne.setAttribute("id",U),ne.setAttribute("viewBox","0 0 12 12"),ne.setAttribute("refX","8"),ne.setAttribute("refY","6"),ne.setAttribute("markerWidth","6"),ne.setAttribute("markerHeight","6"),ne.setAttribute("orient","auto");let J=document.createElementNS("http://www.w3.org/2000/svg","path");J.setAttribute("d","M 1 2 L 10 6 L 1 10 z"),J.setAttribute("fill","#ef4444"),J.setAttribute("stroke","#ffffff"),J.setAttribute("stroke-width","1"),ne.appendChild(J),E.appendChild(ne);let X=document.createElementNS("http://www.w3.org/2000/svg","line");X.setAttribute("x1",`${h}%`),X.setAttribute("y1",`${P}%`),X.setAttribute("x2",`${T}%`),X.setAttribute("y2",`${A}%`),X.setAttribute("stroke","#ffffff"),X.setAttribute("stroke-width","4"),X.setAttribute("stroke-linecap","round"),X.setAttribute("opacity","0.8");let W=document.createElementNS("http://www.w3.org/2000/svg","line");W.setAttribute("x1",`${h}%`),W.setAttribute("y1",`${P}%`),W.setAttribute("x2",`${T}%`),W.setAttribute("y2",`${A}%`),W.setAttribute("stroke","#ef4444"),W.setAttribute("stroke-width","2.5"),W.setAttribute("stroke-linecap","round"),W.setAttribute("marker-end",`url(#${U})`),W.setAttribute("stroke-dasharray","4,4"),N.appendChild(X),N.appendChild(W)}function am(n,t,e,o,i,r,a,s=!1,c="swipe"){let p=s||n<=1e3&&t<=1e3&&e<=1e3&&o<=1e3,f=p?n/10:n/(i||1080)*100,_=p?t/10:t/(r||2400)*100,C=p?e/10:e/(i||1080)*100,h=p?o/10:o/(r||2400)*100,P=Math.max(1.5,Math.min(98.5,f)),T=Math.max(1.5,Math.min(98.5,_)),A=Math.max(1.5,Math.min(98.5,C)),N=Math.max(1.5,Math.min(98.5,h)),E=a.querySelector("svg");E||(E=document.createElementNS("http://www.w3.org/2000/svg","svg"),E.setAttribute("width","100%"),E.setAttribute("height","100%"),E.style.position="absolute",E.style.top="0",E.style.left="0",E.style.pointerEvents="none",E.style.overflow="visible",E.style.zIndex="5",a.appendChild(E));let U=E.querySelector("defs");U||(U=document.createElementNS("http://www.w3.org/2000/svg","defs"),E.insertBefore(U,E.firstChild));let ne="action-arrow-"+Math.random().toString(36).substring(2,9),J=document.createElementNS("http://www.w3.org/2000/svg","marker");J.setAttribute("id",ne),J.setAttribute("viewBox","0 0 12 12"),J.setAttribute("refX","8"),J.setAttribute("refY","6"),J.setAttribute("markerWidth","8"),J.setAttribute("markerHeight","8"),J.setAttribute("orient","auto");let X=document.createElementNS("http://www.w3.org/2000/svg","path");X.setAttribute("d","M 1 2 L 10 6 L 1 10 z"),X.setAttribute("fill","#ef4444"),X.setAttribute("stroke","#ffffff"),X.setAttribute("stroke-width","1.5"),X.setAttribute("stroke-linejoin","round"),J.appendChild(X),U.appendChild(J);let W=document.createElementNS("http://www.w3.org/2000/svg","line");W.setAttribute("x1",`${P}%`),W.setAttribute("y1",`${T}%`),W.setAttribute("x2",`${A}%`),W.setAttribute("y2",`${N}%`),W.setAttribute("stroke","#ffffff"),W.setAttribute("stroke-width","6"),W.setAttribute("stroke-linecap","round"),W.setAttribute("opacity","0.9");let Me=document.createElementNS("http://www.w3.org/2000/svg","line");Me.setAttribute("x1",`${P}%`),Me.setAttribute("y1",`${T}%`),Me.setAttribute("x2",`${A}%`),Me.setAttribute("y2",`${N}%`),Me.setAttribute("stroke","#ef4444"),Me.setAttribute("stroke-width","3.5"),Me.setAttribute("stroke-linecap","round"),Me.setAttribute("marker-end",`url(#${ne})`),Me.setAttribute("stroke-dasharray","6,4");let Ee=document.createElementNS("http://www.w3.org/2000/svg","circle");Ee.setAttribute("cx",`${P}%`),Ee.setAttribute("cy",`${T}%`),Ee.setAttribute("r","6.5"),Ee.setAttribute("fill","#ef4444"),Ee.setAttribute("stroke","white"),Ee.setAttribute("stroke-width","2");let ft=document.createElementNS("http://www.w3.org/2000/svg","circle");ft.setAttribute("cx",`${P}%`),ft.setAttribute("cy",`${T}%`),ft.setAttribute("r","2.5"),ft.setAttribute("fill","white"),E.appendChild(W),E.appendChild(Me),E.appendChild(Ee),E.appendChild(ft);let Jt=(P+A)/2,Xt=(T+N)/2,$t=document.createElement("div");$t.className="action-point-marker action-line-label",$t.style.position="absolute",$t.style.left=`${Jt}%`,$t.style.top=`${Xt}%`,$t.style.transform="translate(-50%, -50%)",$t.style.pointerEvents="none",$t.style.zIndex="10";let zt=document.createElement("div");zt.innerText=c.toUpperCase(),zt.style.backgroundColor="rgba(15, 23, 42, 0.92)",zt.style.color="white",zt.style.padding="2px 6px",zt.style.borderRadius="4px",zt.style.fontSize="9px",zt.style.fontWeight="bold",zt.style.letterSpacing="0.5px",zt.style.whiteSpace="nowrap",zt.style.boxShadow="0 2px 6px rgba(0,0,0,0.5)",zt.style.border="1px solid rgba(255,255,255,0.3)",$t.appendChild(zt),a.appendChild($t)}var uP=new WeakMap;function at(n){if(!n)return{};if(typeof n=="object"){let t=uP.get(n);if(t!==void 0)return t;let e=gP(n);return uP.set(n,e),e}return gP(n)}function gP(n){if(n.payload){let t=n.payload;if(typeof t=="string")try{t=JSON.parse(t)}catch(e){}if(t&&typeof t=="object")return t.args&&typeof t.args=="object"?t.args:t}if(n.args){let t=n.args;if(typeof t=="string")try{t=JSON.parse(t)}catch(e){}if(t&&typeof t=="object")return t}return{}}function pP(n){if(!n||!n.name)return!1;let e=n.name.replace(/^(_)?(self\.)?exec_/,"").toLowerCase();return["save_note","read_note","list_notes","update_note","append_note"].includes(e)}function Fd(n){if(!n||!n.name)return!1;let e=n.name.replace(/^(_)?(self\.)?exec_/,"").toLowerCase();return["video_analysis","video_analyzer","video_analyzer_pure","spawn_sub_agent","analyze_audio_only"].includes(e)}function sm(n){if(!n||typeof n!="object")return null;let t=Number(n.start),e=Number(n.end);return!Number.isFinite(t)||!Number.isFinite(e)||e<=t?null:{start:t,end:e,category:n.category?String(n.category):void 0,retryable:n.retryable===!0}}function dD(n){let t=n.trim();return t?t.startsWith("CACHED VIDEO ANALYSIS:")?{outcome:"complete",reuse:"full",summary:t.replace(/^CACHED VIDEO ANALYSIS:\s*/,"")}:t.startsWith("PARTIAL VIDEO ANALYSIS")?{outcome:"partial",reuse:"partial",summary:t}:t.startsWith("All sub-agent chunks failed")||t.startsWith("Error:")?{outcome:"failed",summary:t}:t.includes("Analysis is already in progress in another video agent")?{outcome:"waiting",summary:"Another video agent is already analyzing this evidence."}:{outcome:"complete",summary:t}:{}}function Fi(n){if(!Fd(n))return null;let t=at(n),e=n?.payload&&typeof n.payload=="object"?n.payload:{},o=e.result??n.result??null,i=o&&typeof o=="object"?o:{},r=typeof o=="string"?dD(o):{},a=String(i.outcome||e.outcome||r.outcome||"").toLowerCase();["running","recovering","waiting","complete","partial","failed"].includes(a)||(a=n?.status==="failed"||n?.status==="error"?"failed":n?.status==="running"?"running":"complete");let s=i.recovering===!0||i.fallback_used===!0;a==="running"&&s&&(a="recovering");let c=Number(t.start_time??i.requested_range?.start),p=Number(t.end_time??i.requested_range?.end),f=Number.isFinite(c)&&Number.isFinite(p)&&p>c?{start:c,end:p}:sm(i.requested_range),_=Array.isArray(i.completed_ranges)?i.completed_ranges.map(sm).filter(A=>!!A):[],C=Array.isArray(i.failed_ranges)?i.failed_ranges.map(sm).filter(A=>!!A):[],h=Number(i.completed_count??_.length??0),P=Number(i.total_count??_.length+C.length),T={running:"Analyzing screen recording",recovering:"Analyzing unfinished recording segment",waiting:"Waiting for existing video analysis",complete:i.reuse==="full"||r.reuse==="full"?"Reused video analysis":"Analyzed screen recording",partial:"Video analysis partially completed",failed:"Video analysis returned no result"};return{outcome:a,title:T[a],query:String(t.specific_query||t.query||t.prompt||i.query||""),summary:String(i.summary||r.summary||""),reuse:i.reuse||r.reuse||"none",requestedRange:f,completedRanges:_,failedRanges:C,evidenceCount:Number(i.evidence_count||0),completedCount:Number.isFinite(h)?h:0,totalCount:Number.isFinite(P)?P:0,fallbackUsed:i.fallback_used===!0}}function cm(n){let t=Math.max(0,Number(n)||0),e=Math.floor(t/60),o=Math.floor(t%60);return`${String(e).padStart(2,"0")}:${String(o).padStart(2,"0")}`}function fP(n){if(!n)return null;let t=at(n);if(t.video_path||t.video_file||t.file_path||t.file){let e=String(t.video_path||t.video_file||t.file_path||t.file);return e.split("/").pop()||e}if(t.time_description)return String(t.time_description);if(t.start_time!==void 0||t.end_time!==void 0){let e=t.start_time??0,o=t.end_time?`${t.end_time}s`:"end";return`${e}s - ${o}`}return t.purpose?String(t.purpose):"screen_record.mp4"}var uD=new Set(["click","click_sequence","long_press","long_press_on","input","input_text","swipe","scroll","press_key","press_home","press_back","launch_app","manage_app","open_app","focus_and_input_text","focus_and_clear_text","tap","wait_for_delay","wait","run_adb_command","run_short_adb_command"]);function Ld(n){if(!n||!n.name||n.type==="llm_call")return!1;let t=n.name.replace(/^(_)?(self\.)?exec_/,"").toLowerCase();return uD.has(t)}function gD(n){if(!n||!n.name)return!0;let t=n.name.replace(/^(_)?(self\.)?exec_/,"").toLowerCase();return!!(t.includes("safety_net")||t==="report_task_status"||t==="report_status"||t==="submit_task_status")}function mP(n,t){if(!n||!n.name||n.type==="llm_call"||n.type==="agent"||gD(n))return!1;if(t&&t.action_taken&&jn(t.action_taken)&&Ld(n)){let e=(n.agent_name||n.agent||"").toLowerCase();if(!(e.includes("failure")||e.includes("analyzer")||e.includes("diagnos")||e&&e!=="operator"&&e!=="flashrunner"&&e!=="validator"))return!1}return!0}function _P(n){if(!n)return null;let t=(n.agent_name||n.agent||"").toLowerCase();return t.includes("failure")||t.includes("analyzer")?null:t.includes("outputter")?"Outputter":t.includes("validator")?"Validator":t.includes("diagnos")?"Diagnoser":t.includes("explorer")?"Explorer":null}function Vd(n){if(!n)return[];let t=n.filter(_=>_?.type==="llm_call"&&_?.status==="retrying"&&(_?.name==="llm_retry"||_?.name==="llm_retry_group")&&_?.payload?.source==="provider_sdk"&&["google","gemini"].includes(String(_?.payload?.provider||"").toLowerCase())),e=_=>String(_?.payload?.request_id||`legacy-${_?.step_id||"unassigned"}`),o=new Map;for(let _ of t){let C=e(_);o.set(C,[...o.get(C)||[],_])}let i=new Set(n.filter(_=>_?.type==="llm_call"&&_?.status==="failed").map(_=>_?.payload?.request_id).filter(_=>typeof _=="string"&&!!_)),r=_=>{let C=_[0],h=_[_.length-1];return F(S({},C),{trace_id:`llm-retry-group-${C?.payload?.request_id||C?.trace_id||C?.timestamp||"unknown"}`,name:"llm_retry_group",payload:F(S({},h?.payload||{}),{retry_count:_.length,total_delay:_.reduce((P,T)=>P+(Number(T?.payload?.delay)||0),0),retries:_.map(P=>({trace_id:P.trace_id,timestamp:P.timestamp,error:P?.payload?.error||P.error,delay:Number(P?.payload?.delay)||0,provider:P?.payload?.provider,source:P?.payload?.source,request_id:P?.payload?.request_id,scheduled_at:P?.payload?.scheduled_at}))})})},a=new Map;for(let _ of n)_&&_.trace_id&&a.set(_.trace_id,_);let s=_=>(_||"").replace(/^(_)?(self\.)?exec_/,"").toLowerCase().trim(),c=[],p=new Set,f=new Set;for(let _ of n){if(!_||!_.name||_.type==="agent")continue;if(_.type==="llm_call"&&_.status==="retrying"){let P=e(_),T=o.get(P),A=_?.payload?.request_id;T&&!f.has(P)&&!(A&&i.has(A))&&(c.push(r(T)),f.add(P));continue}if(_.type==="llm_call"&&_.status==="failed"){(_.name==="llm_pause"||_?.payload?.pause===!0)&&c.push(_);continue}let C=s(_.name),h=!1;if(_.parent_trace_id){let P=a.get(_.parent_trace_id);P&&s(P.name)===C&&(h=!0)}h||(_.trace_id?p.has(_.trace_id)||(p.add(_.trace_id),c.push(a.get(_.trace_id)||_)):c.push(_))}return pD(c)}function pD(n){let t=new Map,e=[];n.forEach((i,r)=>{if(!Fd(i)){e.push({index:r,tool:i});return}let a=String(i.name||"").replace(/^(_)?(self\.)?exec_/,"").toLowerCase(),s=String(a==="video_analyzer"||a==="video_analyzer_pure"?i.trace_id||i.parent_trace_id||`video-${r}`:i.parent_trace_id||i.trace_id||`video-${r}`),c=t.get(s);c?c.tools.push(i):t.set(s,{firstIndex:r,tools:[i]})});let o=[...e];for(let[i,r]of t){let a=r.tools.map(Fi).filter(E=>!!E);if(!a.length)continue;let s=a.some(E=>E.outcome==="running"||E.outcome==="recovering"),c=a.filter(E=>E.outcome==="complete"),p=a.some(E=>E.outcome==="partial"),f=a.filter(E=>E.outcome==="failed"),_=a.filter(E=>E.outcome==="waiting"),C;s?C=a.some(E=>E.outcome==="recovering")?"recovering":"running":p||c.length>0&&f.length>0?C="partial":c.length>0?C="complete":_.length>0?C="waiting":C="failed";let h=a.map(E=>E.requestedRange).filter(E=>!!E),P=h.length?{start:Math.min(...h.map(E=>E.start)),end:Math.max(...h.map(E=>E.end))}:null,T=c.length>0&&c.every(E=>E.reuse==="full")?"full":a.some(E=>E.reuse!=="none")?"partial":"none",A=r.tools[0],N={outcome:C,reuse:T,requested_range:P,completed_count:c.length,total_count:a.length,evidence_count:a.reduce((E,U)=>E+U.evidenceCount,0),query:a.find(E=>E.query)?.query||"",recovering:C==="recovering",fallback_used:a.some(E=>E.fallbackUsed)};o.push({index:r.firstIndex,tool:F(S({},A),{trace_id:`video-analysis-${i}`,name:"video_analysis",status:C==="running"||C==="recovering"?"running":"success",payload:F(S({},A.payload||{}),{args:F(S({},A.payload?.args||A.args||{}),{start_time:P?.start,end_time:P?.end}),result:N})})})}return o.sort((i,r)=>i.index-r.index),o.map(i=>i.tool)}function hP(n){if(!n||!n.name)return null;let e=(n.payload?.args||n.args)?.key;return e?e.toLowerCase().endsWith(".md")?e:`${e}.md`:null}function CP(n,t=!1){if(!n||!n.name)return"";let e=n.name.replace(/^(_)?exec_/,""),o=e.toLowerCase(),i=at(n);switch(o){case"manage_app":case"launch_app":{let r=i.app_name||i.package_name||i.app||"",a=r?r.charAt(0).toUpperCase()+r.slice(1):"Application",s=i.action?String(i.action).toLowerCase():"";return`${s==="launch"?"Launching":s==="stop"||s==="close"?"Stopping":"Managing"} "${a}"`}case"wait_for_delay":case"wait_delay":case"wait":{let r=i.delay_seconds||i.seconds||i.delay||i.duration;return r?`Waiting for ${r} second${Number(r)>1?"s":""}...`:"Waiting for delay..."}case"wait_for_text":{let r=i.text||i.target_text||"";return r?`Waiting for text "${r}" to appear on screen`:"Waiting for text on screen"}case"input_text":case"input":{let r=i.text||i.input_text||"";return r?`Entering text "${r}" into field`:"Entering text into input field"}case"focus_and_input_text":{let r=i.text||i.input_text||"";return r?`Focusing field and typing "${r}"`:"Focusing field and entering text"}case"focus_and_clear_text":return"Focusing and clearing field text";case"click":case"tap":{let r=i.target_text||i.text||i.query||"";return r?`Tapping on "${r}"`:"Tapping on screen element"}case"click_sequence":return"Executing click sequence";case"long_press":{let r=i.target_text||i.text||"";return r?`Long pressing on "${r}"`:"Long pressing screen element"}case"swipe":{let r=i.action||i.direction||"";return r?`Swiping ${String(r).toUpperCase()} on screen`:"Swiping screen"}case"press_key":{let r=i.key||i.keycode||"";return r?`Pressing key ${String(r).toUpperCase()}`:"Pressing hardware key"}case"save_note":return t?"Creating note":"Saving note";case"read_note":return"Reading note";case"list_notes":return"Browsing all saved notes";case"update_note":return"Updating note";case"append_note":return"Updating note";case"object_detection":{let r=Array.isArray(i.queries)?i.queries.join(", "):i.queries||"";return r?`Locating on screen: "${r}"`:"Locating elements on screen"}case"ask_explorer":{let r=i.query||i.prompt||"";return r?`Searching on screen: "${r}"`:"Searching on screen"}case"report_failure_analysis":{let r=i.reason||i.analysis||"";return r?`Investigating issue: ${r}`:"Investigating execution issue"}case"run_adb_command":case"run_short_adb_command":{let r=i.command||i.cmd||"";return r?`Running command: ${r}`:"Running system command"}case"search_logs":case"read_logs":{let r=i.query||i.filter||"";return r?`Searching logs for "${r}"`:"Analyzing system logs"}case"log_analyzer":case"output_analyzer":return"Analyzing logs";case"diagnoser":case"diagnose":return"Diagnosing issue";case"video_analyzer":case"video_analyzer_pure":return"Analyzing screen recording";case"extract_segment_metadata":{let r=i.start_time!==void 0?`${i.start_time}s`:"",a=i.end_time!==void 0?`${i.end_time}s`:"";return`Cropping screen recording segment${r&&a?` (${r} - ${a})`:r?` (from ${r})`:""}`}case"spawn_sub_agent":{let r=i.specific_query||i.query||i.prompt||"";return r?`Analyzing recording with sub-agent: "${r}"`:"Analyzing recording with sub-agent"}case"analyze_audio_only":{let r=i.specific_query||i.query||"";return r?`Analyzing audio track: "${r}"`:"Analyzing recording audio track"}case"get_step_detail":case"get_step_details":{let r=i.step_no??i.step_number??i.step;return r!==void 0&&r!==""?`Reviewing step ${r}`:"Reviewing step details"}case"get_step_screenshot":{let r=i.step_no??i.step_number??i.step,a=String(i.which||"").toLowerCase()==="post"?"after":"before";return r!==void 0&&r!==""?`Looking at the screen ${a} step ${r}`:"Looking at a step screenshot"}case"probe_device":{let r=i.kind?String(i.kind).replace(/_/g," "):"";return r?`Reading ${r} from the device`:"Reading device state"}case"search_history_for_text":{let r=i.text||i.query||"";return r?`Searching execution trace for "${r}"`:"Searching execution trace"}case"outputter":case"output_synthesis":return"Synthesizing output report";case"web_search":{let r=i.query||"";return r?`Searching web for "${r}"`:"Searching the web"}case"read_url":return"Fetching web page";default:return`Executing ${e.split("_").map(r=>r.charAt(0).toUpperCase()+r.slice(1)).join(" ")}`}}function MP(n){if(!n||!n.name)return"settings";switch(n.name.toLowerCase().replace(/^(_)?exec_/,"")){case"click":case"tap":return"ads_click";case"click_sequence":case"long_press":return"touch_app";case"input_text":case"input":return"keyboard";case"swipe":return"swipe";case"press_key":return"keyboard_tab";case"manage_app":return"open_in_new";case"wait_for_delay":return"timer";case"wait_for_text":return"hourglass_empty";case"object_detection":return"search";case"ask_explorer":return"search";case"report_failure_analysis":return"assessment";case"run_adb_command":case"run_short_adb_command":return"terminal";case"web_search":return"travel_explore";case"read_url":return"language";case"search_logs":case"read_logs":case"log_analyzer":case"output_analyzer":return"receipt_long";case"diagnoser":case"diagnose":return"medical_services";case"video_analyzer":case"video_analyzer_pure":return"video_camera_back";case"extract_segment_metadata":return"crop";case"spawn_sub_agent":return"smart_toy";case"analyze_audio_only":return"graphic_eq";case"get_step_detail":case"get_step_details":return"manage_search";case"get_step_screenshot":return"image_search";case"probe_device":return"sensors";case"search_history_for_text":return"find_in_page";case"outputter":case"output_synthesis":return"assignment_turned_in";default:return"build"}}function bP(n){if(!n||!n.name)return"Tool Call";let t=n.name.replace(/^(_)?exec_/,"");switch(t.toLowerCase()){case"click":case"tap":return"Tapping Element";case"click_sequence":return"Executing Click Sequence";case"long_press":return"Long Pressing Element";case"input_text":case"input":return"Entering Text";case"swipe":case"scroll":{let o=at(n),i=o.direction||o.gesture||(typeof o.action=="string"?o.action:"");return i&&Pt(i)?`Swiping Screen (${String(i).toUpperCase()})`:"Swiping Screen"}case"drag":case"drag_and_drop":return"Dragging Screen";case"press_key":return"Pressing Hardware Key";case"manage_app":case"launch_app":{let o=at(n),i=o.action?String(o.action).toLowerCase():"";return i==="launch"?"Launching Application":i==="stop"||i==="close"?"Stopping Application":"Managing Application"}case"wait_for_delay":case"wait_delay":return"Waiting for Delay";case"wait_for_text":return"Waiting for Text";case"object_detection":return"Locating Elements";case"ask_explorer":return"Searching on Screen";case"report_failure_analysis":return"Investigating Issue";case"run_adb_command":case"run_short_adb_command":return"Running System Command";case"web_search":return"Web Search";case"read_url":return"Fetching Web Page";case"search_logs":case"read_logs":return"Searching Logs";case"log_analyzer":case"output_analyzer":return"Analyzing Logs";case"diagnoser":case"diagnose":return"Diagnosing Issue";case"video_analyzer":case"video_analyzer_pure":return"Analyzing Screen Recording";case"extract_segment_metadata":return"Cropping Screen Recording";case"spawn_sub_agent":return"Delegating Video Analysis";case"analyze_audio_only":return"Analyzing Audio Track";default:return t.replace(/_/g," ").replace(/\b\w/g,o=>o.toUpperCase())}}function lm(n){if(!n||!n.name)return"";let t=at(n),e=n.name.toLowerCase().replace(/^(_)?exec_/,"");if(e==="manage_app"||e==="launch_app"){let o=t.app_name||t.package_name||t.app||"",i=o?o.charAt(0).toUpperCase()+o.slice(1):"",r=t.action?String(t.action).toLowerCase():"",a=r?r.charAt(0).toUpperCase()+r.slice(1):"";return a&&i?`${a} ${i}`:i||a||""}return e==="swipe"&&typeof t.action=="string"&&Pt(t.action)?t.action:e==="wait_for_text"?t.text||"":e==="object_detection"?Array.isArray(t.queries)?t.queries.join(", "):t.queries||"":e==="ask_explorer"||e==="web_search"?t.query||t.prompt||"":e==="read_url"?t.url||"":e==="search_logs"||e==="read_logs"?t.query||t.filter||"":e==="report_failure_analysis"?t.status||t.reason||t.analysis||"":t.target&&typeof t.target=="string"?t.target:t.target&&typeof t.target=="number"?`Element #${t.target}`:t.index!==void 0?`Element #${t.index}`:t.target_text||t.target_class||t.element||t.element_text||(e!=="input_text"?t.text:"")||""}function PP(n){if(!n||!n.name)return"Input";let t=n.name.toLowerCase().replace(/^(_)?exec_/,"");if(t==="wait_for_delay"||t==="wait_delay"||t==="delay"||t==="wait")return"Duration";if(t==="swipe"||t==="scroll"||t==="drag"||t==="drag_and_drop"){let e=at(n),o=e.direction||e.gesture||(typeof e.action=="string"?e.action:"");return o&&Pt(o)?"Direction":"Input"}return t==="press_key"||t==="press_home"||t==="press_back"?"Key":t==="input_text"||t==="input"?"Input Text":"Input"}function dm(n){if(!n||!n.name)return"";let t=at(n),e=n.name.toLowerCase().replace(/^(_)?exec_/,"");if(e==="press_key")return t.key||t.keycode||"";if(e==="swipe"||e==="scroll"||e==="drag"||e==="drag_and_drop"){let o=t.direction||t.gesture||(typeof t.action=="string"?t.action:"");return o&&Pt(o)?String(o).toUpperCase():""}return e==="wait_for_delay"||e==="wait_delay"?t.time_in_ms?`${t.time_in_ms}ms`:t.delay_ms?`${t.delay_ms}ms`:t.time?`${t.time}`:"":e==="long_press"&&t.duration?`Duration: ${t.duration}ms`:e==="input_text"||e==="input"?t.text||t.input_text||"":t.input_text||""}function um(n){if(!n||!n.name)return"";let t=at(n),o=n.name.replace(/^(_)?exec_/,"").toLowerCase(),i=o==="click_sequence"||o==="tap_sequence"||!!(t.sequence||t.normalized_sequence),r=t.normalized_sequence||t.sequence||t.targets||(Array.isArray(t.coordinates)&&Array.isArray(t.coordinates[0])?t.coordinates:null),a=Br(r);if(a&&a.length>0&&(i||a.length>1))return a.map(f=>`[${f[0]}, ${f[1]}]`).join(" \u2192 ");let s=oe(t.normalized_start_coordinates||t.start_coordinates||t.start),c=oe(t.normalized_end_coordinates||t.end_coordinates||t.end);if(s&&c&&s.length===2&&c.length===2)return`[${s[0]}, ${s[1]}] \u2192 [${c[0]}, ${c[1]}]`;let p=oe(t.normalized_coordinates)||oe(t.coordinates)||oe(t.target)||oe(t.action)||oe(t.gesture)||oe(t.point);return p&&Array.isArray(p)?p.length===4?`[${p[0]}, ${p[1]}] \u2192 [${p[2]}, ${p[3]}]`:p.length===2?`[${p[0]}, ${p[1]}]`:p.join(", "):t.x!==void 0&&t.y!==void 0?`[${t.x}, ${t.y}]`:""}function gm(n){if(!n||!n.name)return"";let t=at(n);return t.analysis||t.reasoning||t.summary||""}function pm(n){if(!n||!n.name)return!1;let t=n.name.toLowerCase().replace(/^(_)?exec_/,"");return t==="run_adb_command"||t==="run_short_adb_command"}function OP(n){return at(n).CommandLine||""}function vP(n){return at(n).Cwd||""}function yP(n){return at(n).RequestedTerminalID||""}function xP(n){if(!n||pm(n)||lm(n)||dm(n)||um(n)||gm(n))return"";let t=at(n);if(!t||typeof t!="object")return"";let e=new Set(["state","controller","ctx","session_id","step_id","trace_id","parent_trace_id","times","delay_ms"]),o=Object.entries(t).filter(([i,r])=>!e.has(i)&&r!==void 0&&r!==null&&r!=="");return o.length===0?"":o.map(([i,r])=>`${i}: ${typeof r=="object"?JSON.stringify(r):r}`).join(", ")}function wP(n){if(!n)return!1;if(n.status==="failed"||n.status==="error")return!0;let t=at(n);return t.status==="failed"||t.status==="error"||t.status==="cannot_fix"}function SP(n){if(!n)return"Tool Failed";let t=at(n);return t.status==="cannot_fix"?"Status: cannot_fix":t.error||t.message||t.failure_reason?t.error||t.message||t.failure_reason:n.error||n.message?n.error||n.message:"Action Failed"}function kP(n,t){if(!n)return[];let e=n.payload||n.args||n;if(!e||typeof e!="object")return[];if(t&&t.has(e))return t.get(e);let o=new Set(["action","name","type","target_text","text","input_text","target","coordinates","coords","target_bounds","bounds","target_resource_id","resource_id","target_class","class_name","normalized_coordinates","pre_image_name","post_image_name","pre_screenshot","post_screenshot","before_screenshot","after_screenshot","status","success","timestamp","created_at","start_time","execution_id","controller","agent","session_id","step_id","app_name","package_name","app","key","keycode","time_in_ms","delay_ms","delay_seconds","duration","args","kwargs","parameters","extra_params","payload","trace_id","result","error","analysis","details","command","cwd","terminal_id"]),i=[],r=n.args||n.Args||n.kwargs||n.parameters||n.payload,a={};if(r){if(typeof r=="object")a=r;else if(typeof r=="string")try{a=JSON.parse(r)}catch(c){!r.includes("<")&&!r.includes("object at")&&(a={details:r})}}let s=S(S(S({},n),typeof e=="object"?e:{}),a);for(let[c,p]of Object.entries(s)){let f=c.toLowerCase();if(o.has(f)||p==null||p==="")continue;let _=String(p);if(_.includes("object at 0x")||_.startsWith("h.toUpperCase());(f==="time_in_ms"||f==="delay_ms")&&(C="Delay"),i.push({key:C,value:_})}return t&&t.set(e,i),i}function EP(n){if(!n)return!1;let t=n.trim();if(!t)return!1;if(t.startsWith("```")){let e=t.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);e&&(t=e[1].trim())}if(t.startsWith("{")||t.startsWith("["))return!1;try{let e=JSON.parse(t);if(typeof e=="object"&&e!==null)return!1}catch(e){}return!(/^\s*\{.*"label"\s*:.*"results"\s*:/s.test(t)||/^\s*\{.*"point"\s*:.*"label"\s*:/s.test(t)||/^\s*\{.*"action"\s*:/s.test(t)||/^\s*\{.*"name"\s*:/s.test(t)||/^\s*\{\s*"[^"]+"\s*:\s*/.test(t)||/^\s*\[\s*\{\s*"[^"]+"\s*:\s*/.test(t))}function Vn(n){if(typeof n=="object"&&n!==null){if(n.message&&typeof n.message=="string")return Vn(n.message);if(n.error?.message&&typeof n.error?.message=="string")return Vn(n.error.message);if(n.detail&&typeof n.detail=="string")return Vn(n.detail);try{return JSON.stringify(n)}catch(a){return String(n)}}let t=String(n).trim();if(!t)return"Unknown error";let e=t.match(/"message"\s*:\s*"((?:[^"\\]|\\.)*)"/i);if(e&&e[1]){let a=e[1].replace(/\\"/g,'"').replace(/\\n/g," ").trim();if(a)return a}let o=t.match(/'message'\s*:\s*['"]((?:[^'\\]|\\.)*)['"]/i);if(o&&o[1]){let a=o[1].replace(/\\'/g,"'").replace(/\\n/g," ").trim();if(a)return a}let i=t.match(/\{[\s\S]*\}/);if(i)try{let a=i[0].replace(/'/g,'"').replace(/True/g,"true").replace(/False/g,"false").replace(/None/g,"null"),s=JSON.parse(a);if(s?.message)return String(s.message);if(s?.error?.message)return String(s.error.message)}catch(a){}let r=t;return r.includes("=== Source Location Trace")&&(r=r.split("=== Source Location Trace")[0]),r.includes("[type.googleapis.com")&&(r=r.split("[type.googleapis.com")[0]),r=r.replace(/^Pre-execution validation failed:\s*/i,"").replace(/^Pixel-level validation failed:\s*/i,"").replace(/^Execution error:\s*/i,"").replace(/^ServerError:\s*/i,"").replace(/^(?:LLM\s+(?:Request\s+)?Error\s*:\s*)+/i,"").trim(),r||"Unknown error"}function ke(n){return n?Array.isArray(n)?n.length>0?n[0]:null:n:null}function jn(n){if(!n)return!1;let t="";if(typeof n=="string")t=n.toLowerCase();else{let e=ke(n);if(!e)return!1;t=(e.name||e.action||"").toLowerCase()}return["tap","click","click_sequence","input","input_text","swipe","scroll","press_key","press_home","press_back","launch_app","manage_app","open_app","focus_and_input_text","focus_and_clear_text","long_press","long_press_on","wait_for_delay","wait"].includes(t)}function jo(n){if(!n)return!1;let t="";if(typeof n=="string")t=n.toLowerCase();else{let e=ke(n);if(!e)return!1;t=(e.name||e.action||"").toLowerCase()}return t=t.replace(/^(_)?(self\.)?exec_/,""),t==="report_task_status"||t==="report_status"||t==="submit_task_status"}function TP(n){let t=ke(n);if(!t)return"completed";let o=(t.args&&typeof t.args=="object"?t.args:t.payload?.args||t.payload||t).status||t.status;return typeof o=="string"?o.toLowerCase():"completed"}function $d(n){let t=ke(n);if(!t)return"";let e=t.args&&typeof t.args=="object"?t.args:t.payload?.args||t.payload||t;return e.explanation||e.reason||e.summary||t.explanation||t.reason||t.summary||""}function zd(n){let t=ke(n);if(!t)return"ads_click";switch((t.name||t.action||"").toLowerCase()){case"tap":case"click":case"click_sequence":return"ads_click";case"input":case"input_text":case"focus_and_input_text":return"keyboard";case"focus_and_clear_text":case"clear_text":return"backspace";case"swipe":case"scroll":return"swipe";case"drag":case"drag_and_drop":return"drag_indicator";case"press_key":case"press_home":case"press_back":return"keyboard_tab";case"launch_app":case"manage_app":case"open_app":return"open_in_new";case"long_press":case"long_press_on":return"touch_app";case"wait_for_delay":case"delay":case"wait":return"hourglass_empty";default:return"settings"}}function mm(n){let t=ke(n);if(!t)return"Action";let e=(t.name||t.action||"").toLowerCase();switch(e){case"tap":case"click":case"tap_element":case"click_element":return"Tapping Element";case"input":case"input_text":case"focus_and_input_text":return"Entering Text";case"focus_and_clear_text":case"clear_text":return"Clearing Text";case"swipe":case"scroll":{let o=ke(n),i=o?.args&&typeof o.args=="object"?o.args:{},r=o?.direction||o?.gesture||i.direction||i.gesture||(typeof i.action=="string"?i.action:"")||(typeof o?.action=="string"&&o.action!==e?o.action:"");return r&&Pt(r)?`Swiping Screen (${String(r).toUpperCase()})`:"Swiping Screen"}case"drag":case"drag_and_drop":return"Dragging Screen";case"press_key":case"press_home":case"press_back":return"Pressing Hardware Key";case"launch_app":case"open_app":return"Launching Application";case"stop_app":case"close_app":return"Stopping Application";case"manage_app":{let o=ke(n),i=(o?.action||o?.args?.action||"").toLowerCase();return i==="launch"?"Launching Application":i==="stop"||i==="close"?"Stopping Application":"Managing Application"}case"wait_for_delay":case"delay":case"wait":return"Waiting for Delay";case"long_press":case"long_press_on":return"Long Pressing Element";case"click_sequence":return"Clicking Sequence";default:return e.replace(/_/g," ").replace(/\b\w/g,o=>o.toUpperCase())}}function _m(n){let t=ke(n);if(!t)return"";let e=(t.name||t.action||"").toLowerCase(),o=t.args&&typeof t.args=="object"?t.args:{};if(e.includes("app")){let i=t.app_name||t.package_name||t.app||o.app_name||o.package_name||o.app||"",r=i?i.charAt(0).toUpperCase()+i.slice(1):"",a=t.action||o.action||"",s=a?String(a).charAt(0).toUpperCase()+String(a).slice(1).toLowerCase():"";return s&&r?`${s} ${r}`:r||s||""}if(e.includes("delay")||e.includes("wait")){let i=t.time_in_ms||t.delay_ms||t.delay_seconds||o.time_in_ms||o.delay_ms||o.delay_seconds||o.duration;if(i)return`${i}ms`}return t.target_text||t.target_class||t.element_id||o.target_text||o.element_id||""}function AP(n){let t=ke(n);if(!t)return"Input";let e=(t.name||t.action||"").toLowerCase();if(e.includes("delay")||e.includes("wait"))return"Duration";if(e==="swipe"||e==="scroll"||e==="drag"||e==="drag_and_drop"){let o=ke(n),i=o?.args&&typeof o.args=="object"?o.args:{},r=o?.direction||o?.gesture||i.direction||i.gesture||(typeof i.action=="string"?i.action:"")||(typeof o?.action=="string"&&o.action!==e?o.action:"");return r&&Pt(r)?"Direction":"Input"}return e==="press_key"||e.includes("key")?"Key":e==="input_text"||e.includes("input")?"Input Text":"Input"}function NP(n){let t=ke(n);if(!t)return"";let e=(t.name||t.action||"").toLowerCase(),o=t.args&&typeof t.args=="object"?t.args:{};if(e==="press_key"||e.includes("key"))return t.key||t.keycode||o.key||o.keycode||"";if(e==="swipe"||e==="scroll"||e==="drag"||e==="drag_and_drop"){let i=t.direction||t.gesture||o.direction||o.gesture||(typeof o.action=="string"?o.action:"")||(typeof t.action=="string"&&t.action!==e?t.action:"");return i&&Pt(i)?String(i).toUpperCase():""}if(e.includes("delay")||e.includes("wait")){let i=t.time_in_ms||t.delay_ms||t.delay_seconds||o.time_in_ms||o.delay_ms||o.delay_seconds||o.duration;if(i)return`${i}ms`}return t.text||t.input_text||o.text||o.input_text||""}function hm(n){let t=ke(n);if(!t)return"";let e=t.args&&typeof t.args=="object"?t.args:{},o=(t.name||t.action||"").toLowerCase(),i=o==="click_sequence"||o==="tap_sequence"||!!(t.sequence||e.sequence||t.normalized_sequence||e.normalized_sequence),r=t.normalized_sequence||e.normalized_sequence||t.sequence||e.sequence||t.targets||e.targets||(Array.isArray(t.coordinates)&&Array.isArray(t.coordinates[0])?t.coordinates:null)||(Array.isArray(e.coordinates)&&Array.isArray(e.coordinates[0])?e.coordinates:null),a=Br(r);if(a&&a.length>0&&(i||a.length>1))return a.map(h=>`[${h[0]}, ${h[1]}]`).join(" \u2192 ");let s=oe(t.normalized_start_coordinates||e.normalized_start_coordinates),c=oe(t.normalized_end_coordinates||e.normalized_end_coordinates);if(s&&c&&s.length===2&&c.length===2)return`[${s[0]}, ${s[1]}] \u2192 [${c[0]}, ${c[1]}]`;let p=oe(t.start_coordinates||t.start_point||t.start||t.from||e.start_coordinates||e.start_point||e.start||e.from),f=oe(t.end_coordinates||t.end_point||t.end||t.to||e.end_coordinates||e.end_point||e.end||e.to);if(p&&f&&p.length===2&&f.length===2)return`[${p[0]}, ${p[1]}] \u2192 [${f[0]}, ${f[1]}]`;let C=oe(t.normalized_coordinates||e.normalized_coordinates)||oe(t.coordinates)||oe(e.coordinates)||oe(t.coords)||oe(e.coords)||oe(t.target)||oe(e.target)||oe(e.action)||oe(t.action)||oe(e.gesture)||oe(t.gesture);return C&&Array.isArray(C)?C.length===4?`[${C[0]}, ${C[1]}] \u2192 [${C[2]}, ${C[3]}]`:C.length===2?`[${C[0]}, ${C[1]}]`:C.join(", "):""}function Bd(n,t){let e=ke(n);if(e&&(e.status==="failed"||e.status==="error"||e.success===!1))return!0;if(t){if(t.status==="failed"||t.status==="error")return!0;if(t.last_execution_result){let o=typeof t.last_execution_result=="string"?(()=>{try{return JSON.parse(t.last_execution_result)}catch(i){return null}})():t.last_execution_result;if(o&&typeof o=="object"){if(o.status==="failed"||o.status==="error"||o.success===!1||o.is_successful===!1||o.repair_status==="fixed"||o.repair_status==="failed"||o.repair_status==="cannot_fix")return!0;if(Array.isArray(o.execution)&&o.execution.length>0){let i=o.execution[0];if(i&&(i.status==="failed"||i.status==="error"||i.error||Array.isArray(i.attempts)&&i.attempts.length>0))return!0}}}}return!1}function RP(n,t){let e=ke(n);if(e&&(e.error||e.message||e.failure_reason))return Vn(e.error||e.message||e.failure_reason);if(t&&t.last_execution_result){let o=typeof t.last_execution_result=="string"?(()=>{try{return JSON.parse(t.last_execution_result)}catch(i){return null}})():t.last_execution_result;if(o&&typeof o=="object"){if(Array.isArray(o.execution)&&o.execution.length>0){let i=o.execution[0];if(i){if(Array.isArray(i.attempts)&&i.attempts.length>0)return Vn(i.attempts[0]);if(i.error||i.failure_reason||i.message)return Vn(i.error||i.failure_reason||i.message)}}if(o.error||o.message||o.failure_reason)return Vn(o.error||o.message||o.failure_reason)}}return"Action Failed"}function FP(n){let t=ke(n);if(!t)return"";let e=t.target_bounds||t.bounds||t.args&&(t.args.target_bounds||t.args.bounds);return Array.isArray(e)?e.join(", "):e?String(e):""}function LP(n){let t=ke(n);return t&&(t.target_resource_id||t.resource_id||t.args&&(t.args.target_resource_id||t.args.resource_id))||""}function VP(n){let t=ke(n);return t&&(t.target_class||t.class_name||t.args&&(t.args.target_class||t.args.class_name))||""}function jP(n,t){let e=ke(n);if(!e||typeof e!="object")return[];if(t&&t.has(e))return t.get(e);let o=new Set(["action","name","type","target_text","text","input_text","target","coordinates","coords","target_bounds","bounds","target_resource_id","resource_id","target_class","class_name","normalized_coordinates","normalized_start_coordinates","normalized_end_coordinates","start_coordinates","end_coordinates","start","end","from","to","pre_image_name","post_image_name","pre_screenshot","post_screenshot","before_screenshot","after_screenshot","status","success","timestamp","created_at","start_time","execution_id","controller","agent","session_id","step_id","app_name","package_name","app","key","keycode","time_in_ms","delay_ms","delay_seconds","duration","args","kwargs","parameters","extra_params","direction","gesture"]),i=[],r=e.args||e.Args||e.kwargs||e.parameters,a={};if(r){if(typeof r=="object")a=r;else if(typeof r=="string")try{a=JSON.parse(r)}catch(p){!r.includes("<")&&!r.includes("object at")&&(a={details:r})}}let s=S(S({},e),a),c=s.duration||s.duration_ms;c!=null&&c!==""&&i.push({key:"Duration",value:typeof c=="number"?`${c}ms`:String(c)});for(let[p,f]of Object.entries(s)){let _=p.toLowerCase();if(o.has(_)||f==null||f==="")continue;let C=String(f);if(C.includes("object at 0x")||C.startsWith("P.toUpperCase());(_==="time_in_ms"||_==="delay_ms")&&(h="Delay"),i.push({key:h,value:C})}return t&&t.set(e,i),i}function fm(n){if(!n||n==="None"||n==="null"||n==="undefined"||typeof n!="string")return null;let t=n.trim();return!t||t==="None"||t==="null"||t==="undefined"?null:t.startsWith("data:")||t.startsWith("http://")||t.startsWith("https://")||t.startsWith("/local_file")||t.startsWith("/images/")||t.startsWith("/api/images/")?t:t.startsWith("file://")?`/local_file?path=${encodeURIComponent(t)}`:t.startsWith("/")?`/local_file?path=${encodeURIComponent("file://"+t)}`:`/images/${t}`}function fD(n,t){return!n||n===t?.action_taken||Array.isArray(t?.action_taken)&&(t.action_taken===n||t.action_taken.includes(n))||n.is_primary_step_action?!1:!!(n.trace_id||n.payload||n.type==="tool"||n.type==="action"&&n.trace_id)}var DP=new WeakMap;function go(n){if(typeof n=="string"&&(n.trim().startsWith("{")||n.trim().startsWith("[")))try{return JSON.parse(n)}catch(t){return n}return n}function $P(n){let t=ke(n);if(!t)return null;let e=go(t.payload),o=go(t.result||e?.result),i=go(t.args||e?.args),r=o?.pre_image_name||o?.pre_screenshot_name||o?.pre_screenshot||e?.pre_image_name||e?.pre_screenshot_name||e?.pre_screenshot||i?.pre_image_name||i?.pre_screenshot_name||i?.pre_screenshot||t.pre_image_name||t.pre_screenshot_name||t.pre_screenshot||t.before_screenshot||t.screenshot;return r?String(r):null}function jd(n){let t=ke(n);if(!t)return null;let e=go(t.payload),o=go(t.result||e?.result),i=go(t.args||e?.args),r=o?.post_image_name||o?.post_screenshot_name||o?.post_screenshot||e?.post_image_name||e?.post_screenshot_name||e?.post_screenshot||i?.post_image_name||i?.post_screenshot_name||i?.post_screenshot||t.post_image_name||t.post_screenshot_name||t.post_screenshot||t.after_screenshot;return r?String(r):null}function mD(n){if(!n)return null;if(n.failed_screenshot)return String(n.failed_screenshot);if(n.failure_screenshot)return String(n.failure_screenshot);if(n.failed_post_image_name)return String(n.failed_post_image_name);if(Array.isArray(n.generic_tools))for(let t of n.generic_tools){let e=ke(t),o=go(e?.payload),i=go(e?.args||o?.args),r=String(e?.name||e?.agent_name||"").toLowerCase();if(i?.post_screenshot_name)return String(i.post_screenshot_name);if(i?.post_screenshot&&(r.includes("failure")||r.includes("validator")||r.includes("diagnos")))return String(i.post_screenshot);if(e?.post_screenshot_name&&(r.includes("failure")||r.includes("validator")))return String(e.post_screenshot_name)}return null}function IP(n){if(!n)return 0;let t=n.timestamp??n.start_time??n.created_at;if(typeof t=="number")return t<1e11?t*1e3:t;if(typeof t=="string"){let e=new Date(t).getTime();if(!isNaN(e)&&e>0)return e}return 0}function _D(n){let t=new Map;if(!n||typeof n!="object")return t;let e=Array.isArray(n.generic_tools)?n.generic_tools.length:0,o=`${n.step_id||""}_${e}_${n.post_image_name||""}_${n.pre_image_name||""}`,i=DP.get(n);if(i&&i.key===o)return i.map;let r=new Map,a=[];n.action_taken&&a.push({item:n.action_taken,act:ke(n.action_taken),timestamp:IP(n.action_taken),isPrimary:!0}),Array.isArray(n.generic_tools)&&n.generic_tools.forEach(h=>{h&&a.push({item:h,act:ke(h),timestamp:IP(h),isPrimary:!1})}),a.sort((h,P)=>h.timestamp-P.timestamp);let s=mD(n),c=!!s||n.action_taken&&Bd(n.action_taken,n),p=a.some(h=>!h.isPrimary&&(jn(h.act)||!!jd(h.act))),f=null;for(let h=a.length-1;h>=0;h--){let P=a[h];if(jn(P.act)||jd(P.act)||P.isPrimary){f=P.item;break}}let _=n.pre_image_name||n.pre_screenshot||null,C=!1;for(let h of a){let P=h.act,T=$P(P),A=jd(P),N=String(P?.agent_name||P?.agent||go(P?.payload)?.agent_name||P?.name||"").toLowerCase(),E=N.includes("failure")||N.includes("analyzer")||N.includes("validator")||N.includes("diagnos");s&&!C&&(E||!h.isPrimary&&c)&&(_=s,C=!0);let U=T||_,ne=null,J=!1;A?(ne=A,J=!0):(h.isPrimary&&!c||h.item===f&&!c||h.item===f&&E)&&(ne=n.post_image_name||n.post_screenshot||null),ne&&(_=ne);let X={pre:U,post:ne,hasExplicitPost:J};r.set(h.item,X),P&&P!==h.item&&r.set(P,X),P?.trace_id&&r.set(P.trace_id,X)}return DP.set(n,{key:o,map:r}),r}function zP(n,t){let e=_D(n);if(!t)return{pre:n?.pre_image_name||n?.pre_screenshot||null,post:n?.post_image_name||n?.post_screenshot||null,hasExplicitPost:!!(n?.post_image_name||n?.post_screenshot)};let o=ke(t);if(e.has(t))return e.get(t);if(o&&e.has(o))return e.get(o);if(o?.trace_id&&e.has(o.trace_id))return e.get(o.trace_id);let i=$P(o),r=jd(o),a=i||n?.pre_image_name||n?.pre_screenshot||null,s=r||(fD(t,n)?null:n?.post_image_name||n?.post_screenshot||null);return{pre:a,post:s,hasExplicitPost:!!r}}function Cm(n,t){let e=zP(n,t);return fm(e.pre)}function Mm(n,t){let e=zP(n,t),o=fm(e.post),i=fm(e.pre);return o&&i&&o===i&&!e.hasExplicitPost?null:o}function BP(n){if(!Array.isArray(n)||n.length===0)return[];let t=[];for(let s of n)s&&((s.type==="step_updated"||s.type==="step_recorded"||s.type==="step")&&s.data?t.push(s.data):(s.step_number!==void 0||s.step_id!==void 0)&&t.push(s));let e=[],o=new Map,i=new Map;for(let s of t){let c=s.step_id!==void 0&&s.step_id!==null&&String(s.step_id).trim()!==""?String(s.step_id).trim():null,p=s.step_number!==void 0&&s.step_number!==null?Number(s.step_number):null,f=-1;if(c&&o.has(c)?f=o.get(c):p!==null&&i.has(p)&&(f=i.get(p)),f>=0){let _=e[f],C=_.action_taken,h=s.action_taken,P=C&&jn(C)&&jo(h),T=h&&jn(h)&&jo(C),A=p!==null?p:_.step_number,N=_.step_id||s.step_id,E=F(S(S({},_),s),{step_id:N,step_number:A,pre_image_name:P?_.pre_image_name:s.pre_image_name||_.pre_image_name,post_image_name:P?_.post_image_name||s.post_image_name:s.post_image_name||_.post_image_name,pre_screenshot:P?_.pre_screenshot:s.pre_screenshot||_.pre_screenshot,post_screenshot:P?_.post_screenshot||s.post_screenshot:s.post_screenshot||_.post_screenshot,action_taken:P?_.action_taken:T?h:s.action_taken||_.action_taken,last_execution_result:s.last_execution_result||_.last_execution_result,summary:P?_.summary||s.summary:s.summary||_.summary,operator_raw_thinking:s.operator_raw_thinking||_.operator_raw_thinking,operator_native_thinking:s.operator_native_thinking||_.operator_native_thinking,generic_tools:s.generic_tools||_.generic_tools,timestamp:_.timestamp||s.timestamp});e[f]=E,N&&o.set(String(N),f),A!=null&&i.set(Number(A),f)}else{let _=e.length;e.push(S({},s)),c&&o.set(c,_),p!==null&&i.set(p,_)}}let r=[];for(let s of e){let c=s.action_taken,p=Cm(s,c),f=Mm(s,c),_=p||f;_&&r.push({stepData:s,act:c,preUrl:p,postUrl:f,primaryImg:_})}r.sort((s,c)=>{let p=Number(s.stepData.step_number??99999),f=Number(c.stepData.step_number??99999);return p!==f?p-f:Number(s.stepData.timestamp??0)-Number(c.stepData.timestamp??0)});let a=[];for(let s=0;s{let t=this.rawSessions(),e=this.pendingQueue(),o=this.agentStatus(),i=this.runningGoal(),r=this.runningSessionId(),a=this.activeTasks(),s=new Map,c=new Map(e.map(f=>[f.session_id,f])),p=new Map(a.map(f=>[f.session_id,f]));if(t.forEach(f=>{let _=p.get(f.session_id),C=c.get(f.session_id),h=(o==="running"||o==="paused")&&r===f.session_id||!!_,P=f.status==="completed"||f.status==="success"||f.status==="failed"||f.status==="cancelled",T=f.status;P?T=f.status==="success"?"completed":f.status:T=h?o==="paused"?"paused":"running":!!C&&!h?"pending":f.status;let A=f.device_serial||f.device_id||_?.device_id||C?.device_serial||null;if(!A&&f.device_info)try{let E=typeof f.device_info=="string"?JSON.parse(f.device_info):f.device_info;A=E?.device_id||E?.device_serial||null}catch(E){}let N=F(S({},f),{status:T,device_serial:A,model_info:h&&this.activeModel()?this.activeModel():f.model_info});s.set(f.session_id,N),P?this.activeSessionTracking.delete(f.session_id):(T==="running"||T==="paused"||T==="pending")&&this.activeSessionTracking.set(f.session_id,N)}),e.forEach(f=>{if(!s.has(f.session_id)){let _=p.get(f.session_id),C=(o==="running"||o==="paused")&&r===f.session_id||!!_,h=F(S({},f),{status:C?o==="paused"?"paused":"running":f.status||"pending",device_serial:f.device_serial||f.device_id||_?.device_id||null});s.set(f.session_id,h),this.activeSessionTracking.set(f.session_id,h)}}),a.length>0)a.forEach(f=>{let _=f.session_id||`active-${f.device_id}`,C=s.get(_);if(C){if(C.status==="pending"){let h=F(S({},C),{status:"running",device_serial:f.device_id||C.device_serial});s.set(_,h),this.activeSessionTracking.set(_,h)}}else{let h={session_id:_,initial_goal:f.goal||i||"",start_time:f.acquired_at?new Date(f.acquired_at).getTime()/1e3:Date.now()/1e3,status:"running",model_info:this.activeModel()||void 0,device_serial:f.device_id||null};s.set(_,h),this.activeSessionTracking.set(_,h)}});else if((o==="running"||o==="paused")&&r&&!s.has(r)){let f={session_id:r,initial_goal:i||"",start_time:Date.now()/1e3,status:o,model_info:this.activeModel()||void 0};s.set(r,f),this.activeSessionTracking.set(r,f)}return this.activeSessionTracking.forEach((f,_)=>{s.has(_)||s.set(_,F(S({},f),{status:"running"}))}),Array.from(s.values()).sort((f,_)=>_.start_time-f.start_time)});currentSessionId=x(null);sessionLogs=x([]);isSessionContentLoading=x(!1);agentStatus=x("idle");runningSessionId=x(null);runningGoal=x(null);isPaused=x(!1);pausedError=x(null);isRetrying=x(!1);retryMessage=x(null);activeModel=x(null);userPinnedSessionId=x(null);startupProgressBySession=x({});pendingStartupProgress=x([]);currentStartupProgress=I(()=>{let t=this.currentSessionId();if(t){let e=String(t).trim().toLowerCase(),o=this.startupProgressBySession(),i=o[t];if(i?.length)return i;let r=Object.entries(o).find(([a])=>a.trim().toLowerCase()===e);if(r?.[1]?.length)return r[1]}return this.pendingStartupProgress()});currentNotes=x({});selectedNoteKey=x("task_plan.md");activeTab=x("tasks");isVideoWindowOpen=x(!1);isVideoMinimized=x(!1);activeVideoUrl=x(null);activeVideoSegments=x([]);activeVideoTitle=x("");isVideoLoading=x(!1);recordingPlaybackStatus=x("idle");recordingPlaybackMessage=x("");shouldAutoplayVideo=x(!1);videoSeekRequest=x(null);stepSeekRequest=x(null);playerMode=x("video");activeVideoSessionId=null;videoSeekRequestId=0;stepSeekRequestId=0;videoRequestGeneration=0;videoRetryTimer=null;videoWaitStartedAt=0;stepLogsForReplay=I(()=>this.sessionLogs().filter(t=>t&&(t.type==="step_updated"||t.type==="step_recorded")),{equal:(t,e)=>t.length===e.length&&t.every((o,i)=>o===e[i])});currentSessionStepFrames=I(()=>BP(this.stepLogsForReplay()));hasCurrentSessionStepFrames=I(()=>this.currentSessionStepFrames().length>0);setPlayerMode(t){this.playerMode.set(t)}togglePlayerMode(){this.playerMode.update(t=>t==="video"?"steps":"video")}requestStepSeek(t){this.stepSeekRequestId++,this.stepSeekRequest.set({index:t,requestId:this.stepSeekRequestId})}clearUserPinnedSession(){this.userPinnedSessionId.set(null)}currentSession=I(()=>{let t=this.currentSessionId();return t&&this.sessions().find(e=>e.session_id===t)||null});isCurrentSessionRunning=I(()=>{let t=this.currentSessionId();if(!t)return!1;let e=this.currentSession();return e?e.status==="running"||e.status==="paused":!!((this.agentStatus()==="running"||this.agentStatus()==="paused")&&this.runningSessionId()===t||this.activeTasks().some(i=>i.session_id===t))});currentSessionVideoUrl=I(()=>{let t=this.currentSessionId();return t&&this.sessions().find(o=>o.session_id===t)?.video_url||null});currentSessionRecordingStatus=I(()=>{let e=this.currentSession()?.recording_status;return e==="recording"||e==="finalizing"||e==="processing"?"processing":e});eventSource=null;statusInterval=null;sessionLoadGeneration=0;sessionSnapshotRequestId=0;sessionSnapshotAppliedId=0;pendingSnapshotRequests=new Set;pendingStreamChunks=new Map;streamFlushTimer=null;lastQueueSignature=null;lastActiveTasksSignature=null;lastPersistedSessionsJson=null;onVisibilityChange=()=>{typeof document<"u"&&!document.hidden&&(this.fetchStatus(),this.fetchSessions())};constructor(){this.restoreSessionsCache(),this.fetchSessions(),this.startStatusPolling(),this.ensureLiveStream(),typeof document<"u"&&document.addEventListener("visibilitychange",this.onVisibilityChange),D(Ae).onDestroy(()=>this.destroy())}isRunningTask=I(()=>this.agentStatus()==="running"||this.agentStatus()==="paused"?!0:this.sessions().some(t=>t.status==="running"||t.status==="paused"));getProTuningDefaults(){return this.http.get("/api/run/defaults")}runTask(t,e="flash",o,i,r){return new ie(a=>{let s={stage:"submitting",message:"Submitting the task",timestamp:Date.now()/1e3};this.pendingStartupProgress.set([s]);let c={goal:t,profile:e};o&&o.trim()&&(c.expected_output=o.trim()),i!==void 0&&(c.enable_outputter=i),r?.verificationLevel&&(c.verification_level=r.verificationLevel),r?.explorerMode&&(c.explorer_mode=r.explorerMode),this.clearUserPinnedSession(),this.http.post("/api/run",c).subscribe({next:p=>{if(p&&p.tasks&&p.tasks.length>0){let f=p.tasks[0].session_id;if(f){this.appendStartupProgress(F(S({},s),{session_id:f}),f),this.pendingStartupProgress.set([]);let _=this.agentStatus()==="running"||this.sessions().some(h=>h.status==="running"),C=this.runningSessionId()||this.sessions().find(h=>h.status==="running"||h.status==="paused")?.session_id;(!_||C===f)&&this.selectSession(f,!1)}}this.pendingStartupProgress.set([]),a.next(p),a.complete()},error:p=>{this.pendingStartupProgress.set([]),a.error(p)}})})}stopTask(t,e=!1){let o=null,i=e;typeof t=="boolean"?i=t:typeof t=="string"&&t.trim()?o=t.trim():o=this.currentSessionId()||this.runningSessionId()||null,!i&&!o&&(o=this.sessions().find(c=>c.status==="running"||c.status==="paused")?.session_id||null),o&&this.setSessionStatus(o,"cancelled");let r=this.sessions().filter(c=>(c.status==="running"||c.status==="paused")&&c.session_id!==o);i||r.length===0?(this.agentStatus.set("idle"),this.runningSessionId.set(null),this.runningGoal.set(null)):this.runningSessionId()===o&&(this.runningSessionId.set(r[0].session_id),this.runningGoal.set(r[0].initial_goal||null)),this.isPaused.set(!1),this.pausedError.set(null),this.isRetrying.set(!1),this.invalidateStatusSignatures(),i&&this.pendingQueue.set([]),(!o||this.currentSessionId()===o)&&(this.flushStreamChunks(),this.sessionLogs.update(c=>c.some(p=>p.type==="llm_stream"&&!p.data?.isCompleted)?c.map(p=>p.type==="llm_stream"&&!p.data?.isCompleted?F(S({},p),{data:F(S({},p.data),{isCompleted:!0})}):p):c)),o&&this.activeTasks.update(c=>c.filter(p=>p.session_id!==o));let a=`/api/stop?all=${i}`;!i&&o&&(a+=`&session_id=${encodeURIComponent(o)}`);let s={all:i};o&&(s.session_id=o),this.http.post(a,s).subscribe({next:()=>{this.fetchStatus(),this.fetchSessions()},error:c=>{console.error("Failed to stop task:",c),this.fetchSessions(),this.fetchStatus()}})}resumeTask(){this.http.post("/api/resume",{}).subscribe({next:t=>{if(t?.status!=="resumed"){this.fetchStatus();return}let e=this.runningSessionId();this.isPaused.set(!1),this.pausedError.set(null),this.agentStatus.set("running"),this.setSessionStatus(e,"running"),this.fetchStatus()},error:t=>{console.error("Failed to resume task:",t)}})}fetchSessions(){this.http.get("/api/sessions").subscribe({next:t=>{this.rawSessions.set(t),this.persistSessionsCache(t),this.updateModelForCurrentView(t),!this.currentSessionId()&&!this.userPinnedSessionId()&&this.agentStatus()!=="running"&&t.length>0&&this.selectSession(t[0].session_id,!1)},error:t=>{console.error("Failed to fetch sessions from backend:",t)}})}deleteSession(t){if(this.invalidateStatusSignatures(),this.rawSessions.update(e=>e.filter(o=>o.session_id!==t)),this.persistSessionsCache(this.rawSessions()),this.pendingQueue.update(e=>e.filter(o=>o.session_id!==t)),this.userPinnedSessionId()===t&&this.userPinnedSessionId.set(null),this.currentSessionId()===t){this.selectSession("",!1),this.isVideoWindowOpen()&&this.closeVideoPlayer();let e=this.runningSessionId();e&&this.agentStatus()==="running"&&this.selectSession(e,!1)}return new ie(e=>{this.http.post(`/api/sessions/${t}/delete`,{}).subscribe({next:o=>{this.fetchSessions(),this.fetchStatus(),e.next(o),e.complete()},error:o=>{console.error(`Failed to delete session ${t}:`,o),this.fetchSessions(),this.fetchStatus(),e.error(o)}})})}clearAllHistory(){return this.invalidateStatusSignatures(),this.userPinnedSessionId.set(null),this.rawSessions.set([]),this.clearSessionsCache(),this.pendingQueue.set([]),this.selectSession("",!1),this.isVideoWindowOpen()&&this.closeVideoPlayer(),new ie(t=>{this.http.post("/api/cleanup",{}).subscribe({next:e=>{this.fetchSessions(),this.fetchStatus(),t.next(e),t.complete()},error:e=>{console.error("Failed to cleanup history:",e),this.fetchSessions(),this.fetchStatus(),t.error(e)}})})}selectSession(t,e=!1){if(e&&(t&&t===this.runningSessionId()&&this.agentStatus()==="running"?this.userPinnedSessionId.set(null):t?this.userPinnedSessionId.set(t):this.userPinnedSessionId.set(null)),!t){this.sessionLoadGeneration++,this.pendingSnapshotRequests.clear(),this.discardPendingStreamChunks(),this.isSessionContentLoading.set(!1),this.currentSessionId.set(null),this.sessionLogs.set([]),this.currentNotes.set({});return}if(this.currentSessionId()===t)return;let o=++this.sessionLoadGeneration;this.pendingSnapshotRequests.clear(),this.discardPendingStreamChunks(),this.sessionSnapshotAppliedId=0,this.isSessionContentLoading.set(!0),this.currentSessionId.set(t),this.sessionLogs.set([]),this.activePauseCardKey=null,this.isPaused.set(!1),this.pausedError.set(null),this.fetchNotes(t),this.fetchChecks(t),this.updateModelForCurrentView(),this.isVideoWindowOpen()&&this.openVideoPlayer(t),console.debug(`Selecting session: ${t}`),this.backfillSessionSteps(t,o),this.ensureLiveStream()}ensureLiveStream(){this.eventSource||(console.debug("Establishing persistent unified live stream via /api/stream"),this.zone.runOutsideAngular(()=>{this.eventSource=new EventSource("/api/stream"),this.eventSource.addEventListener("info",()=>{let e=this.currentSessionId();e&&this.backfillSessionSteps(e)}),this.eventSource.addEventListener("keep-alive",()=>{}),["llm_stream","trace_recorded","step_recorded","step_updated","background_tasks_updated","task_paused","task_resumed","llm_retrying","startup_progress","session_started","session_ended","recording_ready","recording_failed","checker_event"].forEach(e=>{this.eventSource?.addEventListener(e,o=>{try{let i=JSON.parse(o.data),r=i?.session_id;if(e!=="llm_stream"&&this.flushStreamChunks(),e==="recording_ready"||e==="recording_failed"){this.isVideoWindowOpen()&&r&&String(r)===String(this.activeVideoSessionId)&&(e==="recording_ready"?this.refreshActiveRecording(!0):(this.cancelVideoRetry(),this.isVideoLoading.set(!1),this.activeVideoUrl.set(null),this.activeVideoSegments.set([]),this.recordingPlaybackStatus.set("failed"),this.recordingPlaybackMessage.set(i?.error||"Recording finalization failed.")));return}if(e==="background_tasks_updated"){this.fetchStatus();return}if(e==="session_started"){this.agentStatus.set("running"),i?.session_id&&this.runningSessionId.set(i.session_id),i?.initial_goal&&this.runningGoal.set(i.initial_goal),this.fetchSessions(),!this.userPinnedSessionId()&&i?.session_id&&this.selectSession(i.session_id,!1);return}if(e==="session_ended"){let s=r||this.runningSessionId();s&&(this.applySessionEndedStatus(s,i),this.invalidateStatusSignatures(),this.activeTasks.update(p=>p.filter(f=>f.session_id!==s)));let c=this.activeTasks();if(c.length>0)this.agentStatus.set("running"),this.runningSessionId.set(c[0].session_id),this.runningGoal.set(c[0].goal||null);else if(this.pendingQueue().length>0){let p=this.pendingQueue()[0];this.agentStatus.set("running"),this.runningSessionId.set(p.session_id),this.runningGoal.set(p.initial_goal||null)}else this.agentStatus.set("idle"),this.runningSessionId.set(null),this.runningGoal.set(null);this.isRetrying.set(!1),this.isPaused.set(!1),this.fetchSessions(),this.fetchStatus(),s&&(this.fetchNotes(s),String(s)===String(this.currentSessionId()||"")&&this.fetchChecks(s),this.isVideoWindowOpen()&&this.activeVideoSessionId===s&&this.recordingPlaybackStatus()==="live"&&this.beginRecordingFinalization(s)),this.updateModelForCurrentView();return}if(e==="startup_progress"){let s=r||this.currentSessionId();if(s){this.appendStartupProgress(i,s);let c=this.currentSessionId();!this.userPinnedSessionId()&&(!c||String(s).trim().toLowerCase()!==String(c).trim().toLowerCase())&&(this.agentStatus.set("running"),this.runningSessionId.set(s),this.selectSession(s,!1))}return}let a=this.currentSessionId();if(r&&a&&String(r).trim().toLowerCase()!==String(a).trim().toLowerCase())return;if(e==="llm_retrying"){this.isRetrying.set(!0);let s=Number(i.attempt||0),c=Number(i.max_retries||0),p=Number(i.delay||0),f=s&&c?` (Attempt ${s}/${c})`:"",_=p>0?`; retrying in ${p.toFixed(2).replace(/\.00$/,"")}s`:"";this.retryMessage.set(`AI service is temporarily busy${f}${_}...`),a&&this.appendLiveLLMRetryTrace(i,a);return}if(e==="task_paused"){if(this.isPaused.set(!0),this.isRetrying.set(!1),this.agentStatus.set("paused"),a){this.runningSessionId.set(a),this.setSessionStatus(a,"paused");let s=i.error||"AI call failed";this.pausedError.set(s),this.appendPausedErrorCard(s,a,i.timestamp,i.step_id,i)}return}if(e==="task_resumed"){this.isPaused.set(!1),this.isRetrying.set(!1),this.pausedError.set(null),a&&this.runningSessionId()===a&&(this.agentStatus.set("running"),this.setSessionStatus(a,"running")),this.activePauseCardKey=null;return}if((e==="step_updated"||e==="step_recorded"||e==="llm_stream")&&this.isRetrying.set(!1),e==="llm_stream"?this.queueStreamChunk(i):this.sessionLogs.update(s=>{let p=s.some(C=>C.type==="llm_stream"&&!C.data.isCompleted)?s.map(C=>C.type==="llm_stream"&&!C.data.isCompleted?F(S({},C),{data:F(S({},C.data),{isCompleted:!0})}):C):[...s],f=typeof i?.timestamp=="number"?new Date(i.timestamp*1e3).toISOString():new Date().toISOString(),_={type:e,timestamp:f,data:i};if(e==="trace_recorded"&&i?.trace_id){let C=p.findIndex(h=>h.type==="trace_recorded"&&h.data?.trace_id===i.trace_id);if(C>-1){let h=[...p];return h[C]=_,h}}return[...p,_]}),e==="trace_recorded"&&i){let s=i.name||"";if(["save_note","read_note","update_note","append_note","list_notes","outputter"].includes(s.toLowerCase())){let c=this.currentSessionId();c&&this.fetchNotes(c)}}}catch(i){console.error(`Failed to parse ${e} event data:`,i),this.sessionLogs.update(r=>[...r,{type:e,timestamp:new Date().toISOString(),data:o.data}])}})}),this.eventSource.onerror=e=>{console.warn("Persistent live stream issue, browser will auto-reconnect:",e)}}))}queueStreamChunk(t){let e=t.execution_id,o=t.step_id,i=t.parent_trace_id||null,r=t.stream_type||"text",a=`${e}|${r}`,s=this.pendingStreamChunks.get(a);if(s?(s.chunk+=t.chunk,o&&(s.stepId=o),i&&(s.parentTraceId=i)):this.pendingStreamChunks.set(a,{execId:e,stepId:o,parentTraceId:i,streamType:r,chunk:t.chunk}),!this.streamFlushTimer){let c=typeof document<"u"&&document.hidden?500:80;this.streamFlushTimer=setTimeout(()=>this.flushStreamChunks(),c)}}discardPendingStreamChunks(){this.pendingStreamChunks?.clear(),this.streamFlushTimer&&(clearTimeout(this.streamFlushTimer),this.streamFlushTimer=null)}flushStreamChunks(){if(this.streamFlushTimer&&(clearTimeout(this.streamFlushTimer),this.streamFlushTimer=null),!this.pendingStreamChunks?.size)return;let t=Array.from(this.pendingStreamChunks.values());this.pendingStreamChunks.clear(),this.sessionLogs.update(e=>{let o=[...e];for(let i of t){let r=o.findIndex(a=>a.type==="llm_stream"&&a.data.execution_id===i.execId&&(a.data.stream_type||"text")===i.streamType);if(r>-1){let a=o[r];o[r]=F(S({},a),{data:F(S({},a.data),{text:a.data.text+i.chunk,step_id:i.stepId||a.data.step_id,parent_trace_id:i.parentTraceId||a.data.parent_trace_id||null})})}else o=o.map(a=>a.type==="llm_stream"&&(a.data.stream_type||"text")===i.streamType&&!a.data.isCompleted&&(a.data.parent_trace_id||null)===(i.parentTraceId||null)?F(S({},a),{data:F(S({},a.data),{isCompleted:!0})}):a),o.push({type:"llm_stream",timestamp:new Date().toISOString(),data:{execution_id:i.execId,step_id:i.stepId,parent_trace_id:i.parentTraceId||null,text:i.chunk,stream_type:i.streamType,isCompleted:!1}})}return o})}backfillSessionSteps(t,e=this.sessionLoadGeneration){let o=++this.sessionSnapshotRequestId;this.pendingSnapshotRequests.add(o),this.http.get(`/api/sessions/${t}/steps`).subscribe({next:i=>{if(this.pendingSnapshotRequests.delete(o),this.currentSessionId()!==t||e!==this.sessionLoadGeneration||o({type:"step_updated",session_id:t,timestamp:new Date((a.timestamp||Date.now()/1e3)*1e3).toISOString(),data:a,history_snapshot:!0}));this.sessionLogs.update(a=>[...r,...a.filter(s=>!s.history_snapshot)]),this.isSessionContentLoading.set(!1)},error:i=>{this.pendingSnapshotRequests.delete(o),console.error("Failed to backfill session steps:",i),this.currentSessionId()===t&&e===this.sessionLoadGeneration&&this.pendingSnapshotRequests.size===0&&this.isSessionContentLoading.set(!1)}}),this.http.get(`/api/sessions/${t}/startup_progress`).subscribe({next:i=>{Array.isArray(i)&&i.length>0&&this.startupProgressBySession.update(r=>{let s=[...r[t]||[]];for(let c of i)s.some(p=>p.stage===c.stage)||s.push(c);return F(S({},r),{[t]:s})})},error:()=>{}})}restoreSessionsCache(){try{let t=localStorage.getItem(bm);if(!t)return;let e=JSON.parse(t);Array.isArray(e)&&(this.lastPersistedSessionsJson=t,this.rawSessions.set(e))}catch(t){this.clearSessionsCache()}}persistSessionsCache(t){try{let e=JSON.stringify(t);if(e===this.lastPersistedSessionsJson)return;this.lastPersistedSessionsJson=e,localStorage.setItem(bm,e)}catch(e){}}clearSessionsCache(){this.lastPersistedSessionsJson=null;try{localStorage.removeItem(bm)}catch(t){}}appendLiveLLMRetryTrace(t,e){let o=typeof t?.timestamp=="number"?t.timestamp>1e11?t.timestamp/1e3:t.timestamp:Date.now()/1e3,i=t?.trace_id||`llm-retry-live-${t?.request_id||"unknown"}-${o}`,r={error:t?.error,delay:t?.delay,attempt:t?.attempt,max_retries:t?.max_retries,provider:t?.provider,source:t?.source,recoverable:t?.recoverable,request_id:t?.request_id,scheduled_at:t?.scheduled_at??o},a={trace_id:i,session_id:e,step_id:t?.step_id||null,type:"llm_call",name:"llm_retry",timestamp:o,status:"retrying",payload:r};this.sessionLogs.update(s=>{let c=s.findIndex(_=>_.type==="trace_recorded"&&(_.data?.trace_id===i||t?.request_id&&_.data?.payload?.request_id===t.request_id&&Number(_.data?.payload?.scheduled_at)===Number(r.scheduled_at))),p={type:"trace_recorded",session_id:e,timestamp:new Date(o*1e3).toISOString(),data:a};if(c<0)return[...s,p];let f=[...s];return f[c]=p,f})}appendPausedErrorCard(t,e,o,i,r){let a=typeof t=="string"?t:JSON.stringify(t),s=`${e}:${a}`;this.activePauseCardKey!==s&&(this.sessionLogs.update(c=>{if(c.some(C=>(C?.type==="trace_recorded"?[C.data]:Array.isArray(C?.data?.generic_tools)?C.data.generic_tools:[]).some(P=>P?.type==="llm_call"&&P?.status==="failed"&&String(P?.payload?.error??P?.error??"")===a)))return c;let f=i||[...c].reverse().find(C=>(C.type==="step_updated"||C.type==="step_recorded")&&C.data?.step_id)?.data?.step_id||null,_=typeof o=="number"?o<1e11?o*1e3:o:o?new Date(o).getTime():Date.now();return[...c,{type:"trace_recorded",session_id:e,timestamp:new Date(_).toISOString(),data:{trace_id:`task-paused-${e}-${_}`,session_id:e,step_id:f,type:"llm_call",name:"llm_pause",status:"failed",timestamp:_/1e3,payload:{error:a,pause:!0,request_id:r?.request_id,provider:r?.provider,waited_seconds:r?.waited_seconds,retries:Array.isArray(r?.retries)?r.retries:[]}}}]}),this.activePauseCardKey=s)}pollCounter=0;setSessionStatus(t,e){t&&(this.invalidateStatusSignatures(),this.rawSessions.update(o=>o.map(i=>i.session_id===t?F(S({},i),{status:e}):i)),this.pendingQueue.update(o=>o.map(i=>i.session_id===t?F(S({},i),{status:e}):i)))}applySessionEndedStatus(t,e){if(!t)return;let o=String(e?.status||"").toLowerCase(),i=e?.was_stopped_manually||o==="cancelled"?"cancelled":o==="failed"?"failed":o==="completed"||o==="success"?"completed":null;i&&(this.activeSessionTracking.delete(String(t)),this.setSessionStatus(String(t),i),this.invalidateStatusSignatures(),this.activeTasks.update(r=>r.filter(a=>a.session_id!==String(t))))}startStatusPolling(){this.fetchStatus(),this.zone.runOutsideAngular(()=>{this.statusInterval=setInterval(()=>{typeof document<"u"&&document.hidden||(this.fetchStatus(),this.pollCounter++,this.pollCounter%3===0&&this.fetchSessions())},2e3)})}invalidateStatusSignatures(){this.lastQueueSignature=null,this.lastActiveTasksSignature=null}fetchStatus(){this.http.get("/api/status").subscribe({next:t=>{if(t&&t.status){let e=this.agentStatus(),o=this.runningSessionId(),i=t.status==="running"||t.status==="paused";if(this.agentStatus.set(t.status),this.runningSessionId.set(t.session_id||null),this.runningGoal.set(t.goal||null),t.model_info&&this.activeModel.set(t.model_info),this.isPaused.set(t.status==="paused"),t.status==="paused"){this.isRetrying.set(!1);let s=t.paused_error||this.pausedError()||"AI model request failed. The task is paused.";this.pausedError.set(s),t.session_id&&this.currentSessionId()===t.session_id&&this.appendPausedErrorCard(s,t.session_id)}else this.pausedError.set(null),this.activePauseCardKey=null;let r=JSON.stringify(t.queue||[]);if(r!==this.lastQueueSignature){this.lastQueueSignature=r;let s=(t.queue||[]).map((c,p)=>typeof c=="object"&&c!==null?{session_id:c.session_id||`pending-task-${p}`,initial_goal:c.goal||"",start_time:c.start_time||c.created_at||Date.now()/1e3+p,status:c.status||"pending",device_serial:c.device_serial||c.device_id||null}:{session_id:`task-queued-${p}`,initial_goal:String(c),start_time:Date.now()/1e3+p,status:"pending"});this.pendingQueue.set(s)}let a=JSON.stringify(t.active_tasks||[]);if(a!==this.lastActiveTasksSignature&&(this.lastActiveTasksSignature=a,this.activeTasks.set(t.active_tasks||[])),(e!==t.status||o!==t.session_id)&&this.fetchSessions(),!i&&(e==="running"||e==="paused")&&o&&this.isVideoWindowOpen()&&this.activeVideoSessionId===o&&this.recordingPlaybackStatus()==="live"&&this.beginRecordingFinalization(o),i&&t.session_id){let s=this.currentSessionId();(!s||!this.userPinnedSessionId()&&s!==t.session_id)&&this.selectSession(t.session_id,!1)}}},error:t=>{console.error("Failed to fetch status from backend:",t),this.agentStatus.set("offline"),this.runningSessionId.set(null),this.runningGoal.set(null)}})}updateModelForCurrentView(t){let e=this.currentSessionId(),o=t||this.sessions();if(e){let i=o.find(r=>r.session_id===e);if(i&&i.model_info){this.activeModel.set(i.model_info);return}}}destroy(){this.statusInterval&&(clearInterval(this.statusInterval),this.statusInterval=null),this.eventSource&&(this.eventSource.close(),this.eventSource=null),this.discardPendingStreamChunks(),typeof document<"u"&&document.removeEventListener("visibilitychange",this.onVisibilityChange),this.cancelVideoRetry()}fetchChecks(t){t&&this.http.get(`/api/sessions/${t}/checks`).subscribe({next:e=>{if(this.currentSessionId()!==t)return;let o=this.buildCheckerSnapshotLogs(t,e?.records||[],e?.run_outcome||null);this.sessionLogs.update(i=>[...i.filter(r=>!r.checks_snapshot),...o])},error:e=>{console.error(`Failed to fetch checks for session ${t}:`,e)}})}buildCheckerSnapshotLogs(t,e,o){let i=new Map;for(let a of e){if(!a||!a.attempt_id)continue;let s=String(a.attempt_id),c=String(a.checkpoint_id||""),p=i.get(s);p||(p={event:"attempt_finished",phase:c==="final"?"final":"checkpoint",attempt_id:s,checkpoint_id:c,subgoal_text:String(a.subgoal_text||(c==="final"?"Final review against the user's original goal":`Subgoal ${c.slice(0,8)}`)),anchor_step_id:a.anchor_step_id??null,trace_id:a.trace_id??null,status:"done",verdicts:[],findings:[],ts:typeof a.ts=="number"?a.ts:void 0,session_id:t},i.set(s,p)),typeof a.ts=="number"&&(p.ts=p.ts===void 0?a.ts:Math.min(p.ts,a.ts)),!p.trace_id&&a.trace_id&&(p.trace_id=a.trace_id),p.verdicts.push({item_text:String(a.item_text||""),kind:String(a.kind||""),status:String(a.status||""),evidence:String(a.evidence||""),suggestion:a.suggestion?String(a.suggestion):"",when:a.when?String(a.when):void 0})}let r=[];for(let a of i.values()){let s=new Set(a.verdicts.map(p=>p.status));if(s.size===1){let p=[...s][0];(p==="superseded"||p==="unchecked")&&(a.status=p)}let c=a.ts??Date.now()/1e3;a.timestamp=c,r.push({type:"checker_event",session_id:t,timestamp:new Date(c*1e3).toISOString(),checks_snapshot:!0,data:a})}if(o&&typeof o=="object"){let s=(r.length>0?Math.max(...r.map(c=>new Date(c.timestamp).getTime()/1e3)):Date.now()/1e3)+.001;r.push({type:"checker_event",session_id:t,timestamp:new Date(s*1e3).toISOString(),checks_snapshot:!0,data:F(S({event:"run_outcome",phase:"outcome"},o),{ts:s,timestamp:s,session_id:t})})}return r}fetchNotes(t){if(!t){this.currentNotes.set({});return}this.http.get(`/api/sessions/${t}/notes`).subscribe({next:e=>{if(e&&e.notes){this.currentNotes.set(e.notes);let o=Object.keys(e.notes);if(o.length>0){let i=this.selectedNoteKey();o.includes(i)||(o.includes("task_plan.md")?this.selectedNoteKey.set("task_plan.md"):this.selectedNoteKey.set(o[0]))}}},error:e=>{console.error(`Failed to fetch notes for session ${t}:`,e)}})}openVideoPlayer(t,e,o,i,r){let a=t||this.currentSessionId(),s=this.sessions().find(f=>f.session_id===a),c=e||s?.video_url||null,p=o||s?.initial_goal||(a?`Task: ${a.slice(0,8)}...`:"Screen Recording");if(this.activeVideoTitle.set(p),this.isVideoWindowOpen.set(!0),this.isVideoMinimized.set(!1),this.activeVideoSessionId=a,this.cancelVideoRetry(),this.videoRequestGeneration++,this.shouldAutoplayVideo.set(!1),this.recordingPlaybackMessage.set(""),this.activeVideoSegments.set([]),Number.isFinite(i)&&this.requestVideoSeek(Number(i)),Number.isFinite(r)&&(this.playerMode?.set("steps"),this.requestStepSeek(Number(r))),!a){this.activeVideoUrl.set(null),this.isVideoLoading.set(!1),this.recordingPlaybackStatus.set("unavailable");return}if(s?.status==="running"||s?.status==="paused"){this.activeVideoUrl.set(null),this.isVideoLoading.set(!1),this.recordingPlaybackStatus.set("live");return}this.activeVideoUrl.set(c),this.shouldAutoplayVideo.set(!0),this.videoWaitStartedAt=Date.now(),this.isVideoLoading.set(!0),this.recordingPlaybackStatus.set("processing"),this.recordingPlaybackMessage.set("Loading screen recording..."),c?this.playerMode?.set("video"):this.hasCurrentSessionStepFrames?.()?this.playerMode?.set("steps"):this.playerMode?.set("video"),this.requestSessionVideo(a,this.videoRequestGeneration)}appendStartupProgress(t,e){if(!t?.stage||!t?.message)return;let o=typeof t.timestamp=="number"?t.timestamp>1e11?t.timestamp/1e3:t.timestamp:Date.now()/1e3,i={session_id:String(t.session_id||e),stage:String(t.stage),message:String(t.message),timestamp:o},r=String(t.session_id||e).trim();this.startupProgressBySession.update(a=>{let s=[...a[r]||a[e]||[]],c=s.findIndex(f=>f.stage===i.stage);c>=0?s[c]=i:s.push(i),s.sort((f,_)=>f.timestamp-_.timestamp);let p=F(S({},a),{[r]:s});return e&&e!==r&&(p[e]=s),p})}beginRecordingFinalization(t){this.activeVideoSessionId===t&&(this.cancelVideoRetry(),this.videoRequestGeneration++,this.videoWaitStartedAt=Date.now(),this.activeVideoUrl.set(null),this.activeVideoSegments.set([]),this.shouldAutoplayVideo.set(!0),this.isVideoLoading.set(!0),this.recordingPlaybackStatus.set("processing"),this.recordingPlaybackMessage.set("Finalizing screen recording..."),this.requestSessionVideo(t,this.videoRequestGeneration))}requestSessionVideo(t,e){this.http.get(`/api/sessions/${t}/video`).subscribe({next:o=>{if(e!==this.videoRequestGeneration||t!==this.activeVideoSessionId)return;let i=o.status||(o.has_video&&o.video_url?"ready":"unavailable");if(i==="ready"&&o.video_url){this.cancelVideoRetry(),this.isVideoLoading.set(!1),this.recordingPlaybackStatus.set("ready"),this.recordingPlaybackMessage.set(""),this.activeVideoUrl.set(o.video_url),this.activeVideoSegments.set(o.video_segments||[]),this.playerMode?.set("video"),this.rawSessions.update(r=>r.map(a=>a.session_id===t?F(S({},a),{video_url:o.video_url||void 0,recording_status:"ready"}):a));return}if(i==="processing"){this.activeVideoUrl.set(null),this.activeVideoSegments.set([]),this.isVideoLoading.set(!0),this.recordingPlaybackStatus.set("processing"),this.recordingPlaybackMessage.set("Finalizing screen recording..."),this.scheduleVideoRetry(t,e,o.retry_after_ms);return}this.cancelVideoRetry(),this.isVideoLoading.set(!1),this.activeVideoUrl.set(null),this.activeVideoSegments.set([]),this.recordingPlaybackStatus.set(i==="failed"?"failed":"unavailable"),this.recordingPlaybackMessage.set(o.message||(i==="failed"?"Recording finalization failed.":"No screen recording is available for this task.")),this.hasCurrentSessionStepFrames?.()&&this.playerMode?.set("steps")},error:()=>{if(!(e!==this.videoRequestGeneration||t!==this.activeVideoSessionId)){if(this.recordingPlaybackStatus()==="processing"){this.scheduleVideoRetry(t,e,1e3);return}this.isVideoLoading.set(!1),this.recordingPlaybackStatus.set("failed"),this.recordingPlaybackMessage.set("Unable to load the screen recording."),this.hasCurrentSessionStepFrames?.()&&this.playerMode?.set("steps")}}})}scheduleVideoRetry(t,e,o=1e3){if(this.cancelVideoRetry(),Date.now()-this.videoWaitStartedAt>12e4){this.isVideoLoading.set(!1),this.recordingPlaybackStatus.set("failed"),this.recordingPlaybackMessage.set("Recording finalization timed out. You can retry.");return}let i=Math.max(500,Math.min(3e3,o));this.videoRetryTimer=setTimeout(()=>{this.videoRetryTimer=null,this.requestSessionVideo(t,e)},i)}cancelVideoRetry(){this.videoRetryTimer&&(clearTimeout(this.videoRetryTimer),this.videoRetryTimer=null)}refreshActiveRecording(t){let e=this.activeVideoSessionId;e&&(this.cancelVideoRetry(),this.videoRequestGeneration++,t&&this.shouldAutoplayVideo.set(!0),this.requestSessionVideo(e,this.videoRequestGeneration))}retryVideoRecording(){let t=this.activeVideoSessionId;t&&this.beginRecordingFinalization(t)}consumeVideoAutoplay(){let t=this.shouldAutoplayVideo();return this.shouldAutoplayVideo.set(!1),t}requestVideoSeek(t){Number.isFinite(t)&&this.videoSeekRequest.set({seconds:Math.max(0,t),requestId:++this.videoSeekRequestId})}toggleVideoPlayer(){this.isVideoWindowOpen()?this.isVideoWindowOpen.set(!1):this.openVideoPlayer()}closeVideoPlayer(){this.isVideoWindowOpen.set(!1),this.cancelVideoRetry(),this.videoRequestGeneration++,this.activeVideoSessionId=null}static \u0275fac=function(e){return new(e||n)};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})};var Hd=class n{http=D(Ro);readinessReport=x(null);hasReadinessReport=I(()=>this.readinessReport()!==null);isLoading=x(!1);isRestartingAdb=x(!1);adbServerStatus=x(null);isRemoteAdbServer=I(()=>this.adbServerStatus()?.endpoint.mode==="remote");launchingAvd=x(null);emulatorLaunchState=x(null);isEmulatorLaunching=I(()=>{let t=this.emulatorLaunchState()?.status;return t==="starting"||t==="waiting_for_adb"||t==="booting"});lastCheckedTime=x(null);emulatorPollTimer=null;pythonProbe=I(()=>this.probes().find(t=>t.id==="python_runtime")||null);configProbe=I(()=>this.probes().find(t=>t.id==="system_config")||null);toolchainProbe=I(()=>this.probes().find(t=>t.id==="toolchain")||null);adbProbe=I(()=>this.probes().find(t=>t.id==="android_adb")||null);llmProbe=I(()=>this.probes().find(t=>t.id==="gemini_api_key"||t.id==="llm_api_key")||null);geminiProbe=I(()=>this.llmProbe());ocrProbe=I(()=>this.probes().find(t=>t.id==="vision_ocr_key"||t.id==="ocr_api_key")||null);isEnvironmentReady=I(()=>{let t=this.pythonProbe(),e=this.configProbe(),o=this.adbProbe(),i=this.toolchainProbe(),r=t?.status==="pass",a=e?.status==="pass",s=o?.metadata?.installed??o?.status!=="fail",c=i?.status==="pass"||i?.is_blocker===!1;return r&&a&&s&&c});isSkipCredentialsCheck=x(!1);isCredentialsReady=I(()=>this.isSkipCredentialsCheck()?!0:this.llmProbe()?.status==="pass");isDeviceReady=I(()=>this.adbProbe()?.status==="pass");totalStepCount=I(()=>3);passedStepCount=I(()=>{let t=0;return this.isEnvironmentReady()&&t++,this.isCredentialsReady()&&t++,this.isDeviceReady()&&t++,t});isReady=I(()=>this.isEnvironmentReady()&&this.isCredentialsReady()&&this.isDeviceReady());blockerCount=I(()=>this.totalStepCount());passedBlockerCount=I(()=>this.passedStepCount());probes=I(()=>this.readinessReport()?.probes??[]);activeDevice=I(()=>this.readinessReport()?.active_device??null);osType=I(()=>{let t=this.readinessReport()?.os_type;return t==="windows"||t==="win32"?"windows":t==="darwin"||t==="macos"||t==="mac"?"darwin":"linux"});skipCredentialsCheck(){this.isSkipCredentialsCheck.set(!0)}setSkipCredentialsCheck(t){this.isSkipCredentialsCheck.set(t)}connectedDevices=I(()=>{let t=this.adbProbe()?.metadata;return t&&Array.isArray(t.devices)?t.devices:[]});installedAvds=I(()=>{let t=this.adbProbe()?.metadata;return t&&Array.isArray(t.installed_avds)?t.installed_avds:[]});emulatorPath=I(()=>this.adbProbe()?.metadata?.emulator_path||null);isEmulatorInPath=I(()=>this.adbProbe()?.metadata?.is_emulator_in_path??!0);pollingTimer=null;readinessRequest$=null;zone=D(ge);lastAppliedReportJson=null;onVisibilityChange=()=>{typeof document<"u"&&!document.hidden&&this.fetchReadiness(!0).subscribe({error:()=>{}})};constructor(){this.startAutoPolling(3e3),typeof document<"u"&&document.addEventListener("visibilitychange",this.onVisibilityChange),D(Ae).onDestroy(()=>{this.stopAutoPolling(),this.stopEmulatorStatusPolling(),typeof document<"u"&&document.removeEventListener("visibilitychange",this.onVisibilityChange)})}startAutoPolling(t=3e3){this.pollingTimer&&clearInterval(this.pollingTimer),this.zone.runOutsideAngular(()=>{this.pollingTimer=setInterval(()=>{typeof document<"u"&&document.hidden||this.fetchReadiness(!0).subscribe({error:()=>{}})},t)})}stopAutoPolling(){this.pollingTimer&&(clearInterval(this.pollingTimer),this.pollingTimer=null)}fetchReadiness(t=!1,e=!1){if(t||this.isLoading.set(!0),this.readinessRequest$)return this.readinessRequest$;let o=this.http.get("/api/system/readiness",{params:e?{force:!0}:{}}).pipe(xe({next:i=>{this.applyReadinessReport(i)},error:i=>{console.error("Failed to fetch system readiness:",i)}}),_o(()=>{this.readinessRequest$===o&&(this.readinessRequest$=null),this.isLoading.set(!1)}),Ou({bufferSize:1,refCount:!1}));return this.readinessRequest$=o,o}applyReadinessReport(t){let e=this.readinessReport();if(e&&t.timestamp{this.emulatorLaunchState.set(t),t.status==="ready"?(this.launchingAvd.set(null),this.stopEmulatorStatusPolling(),this.fetchReadiness().subscribe()):t.status==="failed"||t.status==="stopped"||t.status==="idle"?(this.launchingAvd.set(null),this.stopEmulatorStatusPolling()):t.avd_name&&this.launchingAvd.set(t.avd_name)},error:t=>{console.error("Failed to fetch emulator status:",t)}}))}startEmulatorStatusPolling(){this.emulatorPollTimer&&clearInterval(this.emulatorPollTimer),this.emulatorPollTimer=setInterval(()=>{this.fetchEmulatorStatus().subscribe({error:()=>{}})},1e3)}stopEmulatorStatusPolling(){this.emulatorPollTimer&&(clearInterval(this.emulatorPollTimer),this.emulatorPollTimer=null)}launchEmulator(t){return this.launchingAvd.set(t),this.emulatorLaunchState.set({avd_name:t,status:"starting",pid:null,serial:null,stage_message:"Spawning emulator process...",progress_percent:15,started_at:Date.now()/1e3,elapsed_seconds:0,error:null,logs:[`Initiating launch for AVD: ${t}...`],can_retry:!0}),this.http.post("/api/system/emulator/launch",{avd_name:t}).pipe(xe({next:e=>{this.emulatorLaunchState.set(e),e.status==="failed"?this.launchingAvd.set(null):this.startEmulatorStatusPolling()},error:e=>{console.error("Failed to launch emulator:",e),this.launchingAvd.set(null);let o=e?.error?.detail||e?.message||"Failed to start emulator process.";this.emulatorLaunchState.set({avd_name:t,status:"failed",pid:null,serial:null,stage_message:"Failed to initiate launch.",progress_percent:0,started_at:null,elapsed_seconds:0,error:o,logs:[o],can_retry:!0})}}))}stopEmulator(){return this.http.post("/api/system/emulator/stop",{}).pipe(xe({next:()=>{this.launchingAvd.set(null),this.stopEmulatorStatusPolling(),this.fetchEmulatorStatus().subscribe(),this.fetchReadiness().subscribe()}}))}dismissEmulatorStatus(){return this.http.post("/api/system/emulator/dismiss",{}).pipe(xe({next:()=>{this.emulatorLaunchState.set(null),this.launchingAvd.set(null),this.stopEmulatorStatusPolling()}}))}restartAdb(){return this.isRestartingAdb.set(!0),this.http.post("/api/system/adb/restart",{}).pipe(xe({next:t=>{t?.report&&this.applyReadinessReport(t.report),this.isRestartingAdb.set(!1)},error:t=>{console.error("Failed to restart ADB server:",t),this.isRestartingAdb.set(!1)}}))}connectWirelessAdb(t,e=5555){return this.http.post("/api/system/adb/connect",{host:t,port:e}).pipe(xe({next:o=>{o?.report&&this.applyReadinessReport(o.report)}}))}fetchAdbServerStatus(){return this.http.get("/api/system/adb/server").pipe(xe(t=>this.adbServerStatus.set(t)))}probeAdbServer(t,e){return this.http.post("/api/system/adb/server/probe",{host:t,port:e,persist:!1})}connectAdbServer(t,e,o=!0){return this.http.post("/api/system/adb/server/connect",{host:t,port:e,persist:o}).pipe(xe(i=>{i.connection_result.success&&(this.adbServerStatus.set({endpoint:i.connection_result.endpoint}),i.report&&this.applyReadinessReport(i.report))}))}useLocalAdbServer(t=!0){return this.http.post("/api/system/adb/server/local",{},{params:{persist:t}}).pipe(xe(e=>{this.adbServerStatus.set({endpoint:e.connection_result.endpoint}),e.report&&this.applyReadinessReport(e.report)}))}selectDevice(t){return this.isLoading.set(!0),this.http.post("/api/system/devices/select",{serial:t}).pipe(xe({next:e=>{e?.report&&this.applyReadinessReport(e.report),this.isLoading.set(!1)},error:e=>{console.error("Failed to select active device:",e),this.isLoading.set(!1)}}))}currentApiKey=I(()=>this.llmProbe()?.metadata?.current_key||"");apiKeysMap=I(()=>this.llmProbe()?.metadata?.api_keys||{});modelConfigEnv=x(null);fetchModelConfigEnv(){return this.http.get("/api/system/model-config-env").pipe(xe({next:t=>{this.modelConfigEnv.set(t)},error:t=>{console.error("Failed to fetch model config and env:",t)}}))}testApiKey(t,e,o){return this.http.post("/api/system/credentials/test",{provider:t,api_key:e,base_url:o})}updateApiKey(t,e,o=!0){return this.http.post("/api/system/credentials",{provider:t,api_key:e,persist_to_env:o}).pipe(xe({next:i=>{i?.report&&this.applyReadinessReport(i.report),this.fetchModelConfigEnv().subscribe()},error:i=>{console.error(`Failed to update credentials for ${t}:`,i)}}))}static \u0275fac=function(e){return new(e||n)};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})};var HP={"com.google.android.apps.maps":{name:"Maps",icon:"explore",pkg:"com.google.android.apps.maps",category:"navigation"},"com.google.android.gm":{name:"Gmail",icon:"mail",pkg:"com.google.android.gm",category:"productivity"},"com.android.chrome":{name:"Chrome",icon:"public",pkg:"com.android.chrome",category:"browser"},"com.google.android.youtube":{name:"YouTube",icon:"smart_display",pkg:"com.google.android.youtube",category:"entertainment"},"com.android.settings":{name:"Settings",icon:"settings",pkg:"com.android.settings",category:"system"},"com.google.android.deskclock":{name:"Clock",icon:"timer",pkg:"com.google.android.deskclock",category:"utility"},"com.android.deskclock":{name:"Clock",icon:"timer",pkg:"com.android.deskclock",category:"utility"},"com.google.android.calculator":{name:"Calculator",icon:"calculate",pkg:"com.google.android.calculator",category:"utility"},"com.android.calculator2":{name:"Calculator",icon:"calculate",pkg:"com.android.calculator2",category:"utility"},"com.google.android.apps.photos":{name:"Photos",icon:"photo_library",pkg:"com.google.android.apps.photos",category:"media"},"com.google.android.calendar":{name:"Calendar",icon:"calendar_month",pkg:"com.google.android.calendar",category:"productivity"},"com.google.android.keep":{name:"Keep Notes",icon:"note_alt",pkg:"com.google.android.keep",category:"productivity"},"com.android.vending":{name:"Play Store",icon:"storefront",pkg:"com.android.vending",category:"tools"},"com.google.android.apps.messaging":{name:"Messages",icon:"chat",pkg:"com.google.android.apps.messaging",category:"communication"},"com.tencent.mm":{name:"WeChat",icon:"forum",pkg:"com.tencent.mm",category:"social"},"com.xingin.xhs":{name:"Xiaohongshu",icon:"auto_stories",pkg:"com.xingin.xhs",category:"social"},"com.sankuai.meituan":{name:"Meituan",icon:"restaurant",pkg:"com.sankuai.meituan",category:"lifestyle"},"com.dianping.v1":{name:"Dianping",icon:"star",pkg:"com.dianping.v1",category:"lifestyle"},"tv.danmaku.bili":{name:"Bilibili",icon:"video_library",pkg:"tv.danmaku.bili",category:"entertainment"},"com.eg.android.AlipayGphone":{name:"Alipay",icon:"account_balance_wallet",pkg:"com.eg.android.AlipayGphone",category:"finance"},"com.netease.cloudmusic":{name:"NetEase Music",icon:"headphones",pkg:"com.netease.cloudmusic",category:"entertainment"},"com.spotify.music":{name:"Spotify",icon:"music_note",pkg:"com.spotify.music",category:"entertainment"}},UP=[{id:"maps_coffee",title:"Find Specialty Coffee",description:"Search nearby top-rated cafes in Google Maps",goal:"Open Google Maps, search for top-rated specialty coffee shops nearby, and view the top result details.",profile:"flash",category:"flash",tag:"Maps",apps:[{name:"Maps",icon:"explore",pkg:"com.google.android.apps.maps"}],requiredPackages:["com.google.android.apps.maps"],priority:95},{id:"pro_commute_share",title:"Commute ETA & Message Draft",description:"Check transit time on Maps and draft arrival ETA in Messages",goal:"Open Google Maps to check commute time to the International Airport, calculate arrival time, then open Messages and draft an ETA text message.",profile:"pro",category:"cross_app",tag:"Maps + Messages",apps:[{name:"Maps",icon:"explore",pkg:"com.google.android.apps.maps"},{name:"Messages",icon:"chat",pkg:"com.google.android.apps.messaging"}],requiredPackages:["com.google.android.apps.maps","com.google.android.apps.messaging"],matchMode:"all",priority:92},{id:"gmail_receipts",title:"Search Order Receipts",description:"Find recent flight or delivery confirmation emails in Gmail",goal:"Open Gmail and search for recent flight or package delivery confirmation emails.",profile:"flash",category:"flash",tag:"Gmail",apps:[{name:"Gmail",icon:"mail",pkg:"com.google.android.gm"}],requiredPackages:["com.google.android.gm"],priority:90},{id:"pro_email_to_calendar",title:"Email Itinerary to Calendar",description:"Extract flight or event dates from Gmail and schedule in Calendar",goal:"Open Gmail to find the latest event invitation or itinerary, extract dates and location, then open Google Calendar and create a corresponding calendar event.",profile:"pro",category:"cross_app",tag:"Gmail + Calendar",apps:[{name:"Gmail",icon:"mail",pkg:"com.google.android.gm"},{name:"Calendar",icon:"calendar_month",pkg:"com.google.android.calendar"}],requiredPackages:["com.google.android.gm"],priority:94},{id:"chrome_research",title:"Search AI News Breakthroughs",description:"Search latest multimodal AI developments in Chrome browser",goal:"Open Chrome browser and search for latest breakthroughs in multimodal mobile AI agents.",profile:"flash",category:"flash",tag:"Chrome",apps:[{name:"Chrome",icon:"public",pkg:"com.android.chrome"}],requiredPackages:["com.android.chrome"],priority:88},{id:"pro_research_keep",title:"Product Research & Notes Note",description:"Compare top 3 headphones on Chrome and record comparison in Keep",goal:"Open Chrome, research top 3 noise-cancelling headphones comparing price and battery life, then write a structured comparison summary note in Keep Notes.",profile:"pro",category:"pro",tag:"Chrome + Keep",apps:[{name:"Chrome",icon:"public",pkg:"com.android.chrome"},{name:"Keep Notes",icon:"note_alt",pkg:"com.google.android.keep"}],requiredPackages:["com.android.chrome"],priority:91},{id:"youtube_lofi",title:"Play Lo-Fi Music Radio",description:"Search and play a Lo-Fi hip hop live stream on YouTube",goal:'Open YouTube, search for "Lofi hip hop beats relaxing radio" and tap on the live stream.',profile:"flash",category:"flash",tag:"YouTube",apps:[{name:"YouTube",icon:"smart_display",pkg:"com.google.android.youtube"}],requiredPackages:["com.google.android.youtube"],priority:85},{id:"settings_display_wifi",title:"Dark Mode & Wi-Fi Check",description:"Toggle dark theme and verify network connection in Settings",goal:"Open Settings app, navigate to Display settings, ensure Dark theme is enabled, and check Wi-Fi connection status.",profile:"flash",category:"flash",tag:"Settings",apps:[{name:"Settings",icon:"settings",pkg:"com.android.settings"}],requiredPackages:["com.android.settings"],priority:87},{id:"pro_settings_qa",title:"Subsystem Health & Crash Probe",description:"Traverse Settings submenus to verify screens and check for crash dialogs",goal:"Explore Settings submenus (Network, Connected devices, Apps, Battery, Storage), verify each screen loads properly without ANR or crash dialogs, and summarize results.",profile:"pro",category:"monitor",tag:"Settings QA",apps:[{name:"Settings",icon:"settings",pkg:"com.android.settings"}],requiredPackages:["com.android.settings"],priority:93},{id:"clock_timer",title:"25-Min Pomodoro Timer",description:"Start a 25-minute focus countdown timer in Clock app",goal:"Open Clock app, switch to Timer tab, set 25 minutes and start the countdown timer.",profile:"flash",category:"flash",tag:"Clock",apps:[{name:"Clock",icon:"timer",pkg:"com.google.android.deskclock"}],requiredPackages:["com.google.android.deskclock","com.android.deskclock"],priority:86},{id:"calc_gratuity",title:"Split Bill & Calculate Tip",description:"Calculate 18% gratuity on $186.40 for 3 people in Calculator",goal:"Open Calculator and calculate 18% tip on a bill of $186.40, then divide by 3 people.",profile:"flash",category:"flash",tag:"Calculator",apps:[{name:"Calculator",icon:"calculate",pkg:"com.google.android.calculator"}],requiredPackages:["com.google.android.calculator","com.android.calculator2"],priority:84},{id:"photos_inspect",title:"Inspect Recent Screenshot",description:"Open Google Photos and review the latest screenshot taken",goal:"Open Google Photos and view the most recent screenshot in the screenshots album.",profile:"flash",category:"flash",tag:"Photos",apps:[{name:"Photos",icon:"photo_library",pkg:"com.google.android.apps.photos"}],requiredPackages:["com.google.android.apps.photos"],priority:82},{id:"wechat_browse",title:"Check WeChat Messages",description:"Open WeChat and view top recent chat conversations",goal:"Open WeChat and view the top recent chat messages.",profile:"flash",category:"flash",tag:"WeChat",apps:[{name:"WeChat",icon:"forum",pkg:"com.tencent.mm"}],requiredPackages:["com.tencent.mm"],priority:89},{id:"pro_wechat_to_calendar",title:"WeChat Notice to Calendar",description:"Extract meeting notice from WeChat chat and add to Calendar",goal:"Open WeChat, locate the latest meeting announcement or event message in the top chat, extract the time and topic, then open Calendar and schedule an event.",profile:"pro",category:"cross_app",tag:"WeChat + Calendar",apps:[{name:"WeChat",icon:"forum",pkg:"com.tencent.mm"},{name:"Calendar",icon:"calendar_month",pkg:"com.google.android.calendar"}],requiredPackages:["com.tencent.mm"],priority:93},{id:"xhs_coffee_guide",title:"RED Cafe Guide Search",description:"Search trending specialty cafe reviews on Xiaohongshu",goal:"Open Xiaohongshu, search for top-rated specialty coffee shops, and view the top post.",profile:"flash",category:"flash",tag:"Xiaohongshu",apps:[{name:"Xiaohongshu",icon:"auto_stories",pkg:"com.xingin.xhs"}],requiredPackages:["com.xingin.xhs"],priority:87},{id:"meituan_ramen_search",title:"Meituan Food Search",description:"Search top-rated Ramen nearby on Meituan or Dianping",goal:"Open Meituan or Dianping, search for top-rated Ramen nearby, and view top restaurant rating.",profile:"flash",category:"flash",tag:"Meituan",apps:[{name:"Meituan",icon:"restaurant",pkg:"com.sankuai.meituan"}],requiredPackages:["com.sankuai.meituan","com.dianping.v1"],priority:86},{id:"bilibili_stream",title:"Bilibili Tech Video",description:"Search and play an AI Agent tutorial video on Bilibili",goal:'Open Bilibili, search for "AI Agent Architecture", and play the top matching video.',profile:"flash",category:"flash",tag:"Bilibili",apps:[{name:"Bilibili",icon:"video_library",pkg:"tv.danmaku.bili"}],requiredPackages:["tv.danmaku.bili"],priority:85},{id:"pro_playstore_review",title:"Play Store App Review Study",description:"Compare top task management apps and ratings on Google Play",goal:"Open Google Play Store, search for top rated task management apps, compare ratings and latest user reviews of the top 2 candidates, and record recommendations.",profile:"pro",category:"pro",tag:"Play Store",apps:[{name:"Play Store",icon:"storefront",pkg:"com.android.vending"}],requiredPackages:["com.android.vending"],priority:88}];var Ud=class n{allTasks=UP;appRegistry=HP;isSuggestionOnDevice(t,e){return e.size===0?!1:!t.requiredPackages||t.requiredPackages.length===0?!0:t.matchMode==="all"?t.requiredPackages.every(o=>e.has(o)):t.requiredPackages.some(o=>e.has(o))}filterAndRankTasks(t,e,o=0){let i=t.size>0,a=this.allTasks.map(c=>{let p=this.isSuggestionOnDevice(c,t),f=c.priority??50;return i&&(p?(f+=100,c.requiredPackages&&c.requiredPackages.length>1&&c.matchMode==="all"&&(f+=30)):f-=40),{task:c,isMatched:p,score:f}}).filter(c=>e==="flash"?c.task.profile==="flash":e==="pro"?c.task.profile==="pro":e==="cross_app"?c.task.category==="cross_app":e==="monitor"?c.task.category==="monitor":!0);a.sort((c,p)=>p.score-c.score);let s=a.map(c=>c.task);if(o>0&&s.length>0){let c=o%s.length;s=[...s.slice(c),...s.slice(0,c)]}return s}static \u0275fac=function(e){return new(e||n)};static \u0275prov=te({token:n,factory:n.\u0275fac,providedIn:"root"})};var _n=[{id:"off",label:"Off",tagline:"No checking. The run ends as soon as the task looks done.",latency:"no extra time",runs:["Each step is treated as finished the moment it is carried out.","You get the full action trace, but no pass / fail verdict."],skips:["Nothing is double-checked and nothing is retried."],bestFor:"Quick tries and demos, when you only want to watch what happens."},{id:"final",label:"At the end",tagline:"One check of the finished result against your goal. This is the default.",latency:"adds about 20\u201360 s at the end",runs:["When the task finishes, the final screen, the step history and the device state are compared with what you asked for.","If the result does not match, the task goes back and tries to fix it, up to 3 times."],skips:["Nothing is checked while the task is still running."],bestFor:"Everyday tasks: an honest pass / fail without slowing the run down."},{id:"checkpoints",label:"Every step",tagline:"Each step is checked as soon as it is done, plus the final check.",latency:"a short check after each step, done in the background",runs:["Every step is checked right after it completes, using the screenshots from that moment.","If a step went wrong, it gets fixed before moving on (up to 2 tries per step).","A failed test condition is written down and the task keeps going.","The final check still runs at the end."],bestFor:"Long tasks where one early mistake would spoil everything after it."},{id:"strict",label:"Strict",tagline:"Every step is checked, with more retries. The first failed test stops the run.",latency:"slowest: more checks and more retries",runs:["Each check takes longer and gets more attempts: 4 fixes per step and 5 at the end.","The first failed test condition stops the run immediately, with the evidence attached."],bestFor:"Release checks and regression runs, where a wrong pass is never acceptable."}],hn=[{id:"flash",label:"Quick glance",tagline:"Finds buttons and text on the screen in a single look.",latency:"1 look per search",runs:["Something on screen is asked for by name, icon or colour and its position comes back straight away.","Several things can be looked up at once."],skips:["No zooming in and no second try."],bestFor:"Ordinary apps with clearly labelled buttons, icons and text."},{id:"pro",label:"Second look",tagline:"Takes up to 3 looks, thinking in between, before answering.",latency:"up to 3 looks per search",runs:["The screen layout is read first, then the picture is searched.","If the first try misses, a different approach is tried within the 3 looks."],skips:["Still no zooming into small areas, to keep searches short."],bestFor:'Things described by where they are ("the switch next to Wi-Fi") or with unclear labels.'},{id:"ultra",label:"Close-up",tagline:"Zooms into parts of the screen and takes up to 8 looks.",latency:"up to 8 looks per search (slowest)",runs:["Parts of the screen can be cropped and magnified to read tiny text and crowded layouts piece by piece.","Later looks reuse the earlier ones, so they cost less time than they sound."],bestFor:"Crowded screens, tiny targets, charts and drawings, and checks where exact placement matters."}],Hr="final",Ur="flash";function Li(n,t,e){let o=String(t??"").trim().toLowerCase(),i=n.findIndex(r=>r.id===o);return i>=0?i:Math.max(0,n.findIndex(r=>r.id===e))}function Gd(n,t){return t<=1?0:Math.min(Math.max(n,0),t-1)/(t-1)*100}var hD=()=>[],GP=(n,t)=>t.name,Pm=(n,t)=>t.serial,qP=(n,t)=>t.id,CD=(n,t)=>t.kind;function MD(n,t){n&1&&(l(0,"span",14),u(1,"check_circle"),d())}function bD(n,t){n&1&&(l(0,"span",15),u(1,"tune"),d())}function PD(n,t){n&1&&(l(0,"span",17),u(1,"lock"),d())}function OD(n,t){n&1&&(l(0,"span",15),u(1,"rocket_launch"),d())}function vD(n,t){n&1&&(l(0,"span",23),u(1,"check"),d())}function yD(n,t){n&1&&(l(0,"span"),u(1,"1"),d())}function xD(n,t){if(n&1){let e=L();l(0,"div",31)(1,"div",72)(2,"div",73)(3,"span",74),u(4,"bolt"),d()(),l(5,"div",75)(6,"div",76)(7,"div",77),u(8,"Automated Dependency Setup"),d(),l(9,"div",78)(10,"button",79),k("click",function(){v(e);let i=m(2);return y(i.setSelectedOs("linux"))}),u(11,"Linux"),d(),l(12,"button",79),k("click",function(){v(e);let i=m(2);return y(i.setSelectedOs("darwin"))}),u(13,"macOS"),d(),l(14,"button",79),k("click",function(){v(e);let i=m(2);return y(i.setSelectedOs("windows"))}),u(15,"Windows"),d()()(),l(16,"div",80),u(17,"Run in terminal to install ADB, FFmpeg, scrcpy, and project dependencies:"),d()()(),l(18,"div",81)(19,"code"),u(20),d(),l(21,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard(i.oneClickSetupCmd(),"one_click_deps"))}),l(22,"span",29),u(23),d(),l(24,"span"),u(25),d()()()()}if(n&2){let e=m(2);g(10),w("active",e.effectiveOs()==="linux"),g(2),w("active",e.effectiveOs()==="darwin"),g(2),w("active",e.effectiveOs()==="windows"),g(6),O(e.oneClickSetupCmd()),g(),w("copied",e.copiedId()==="one_click_deps"),g(2),O(e.copiedId()==="one_click_deps"?"check":"content_copy"),g(2),O(e.copiedId()==="one_click_deps"?"Copied!":"Copy")}}function wD(n,t){if(n&1){let e=L();l(0,"div",42)(1,"div",83)(2,"div",81)(3,"span",84),u(4,"$"),d(),l(5,"code",85),u(6,"uv python install 3.12"),d(),l(7,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard("uv python install 3.12","py_install"))}),l(8,"span",29),u(9),d(),l(10,"span"),u(11),d()()()()()}if(n&2){let e=m(2);g(7),w("copied",e.copiedId()==="py_install"),g(2),O(e.copiedId()==="py_install"?"check":"content_copy"),g(2),O(e.copiedId()==="py_install"?"Copied!":"Copy")}}function SD(n,t){if(n&1){let e=L();l(0,"div",42)(1,"div",83)(2,"div",81)(3,"span",84),u(4,"$"),d(),l(5,"code",85),u(6),d(),l(7,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard(i.adbInstallCmd(),"env_adb_cmd"))}),l(8,"span",29),u(9),d(),l(10,"span"),u(11),d()()()()()}if(n&2){let e=m(2);g(6),O(e.adbInstallCmd()),g(),w("copied",e.copiedId()==="env_adb_cmd"),g(2),O(e.copiedId()==="env_adb_cmd"?"check":"content_copy"),g(2),O(e.copiedId()==="env_adb_cmd"?"Copied!":"Copy")}}function kD(n,t){if(n&1){let e=L();l(0,"div",42)(1,"div",83)(2,"div",81)(3,"span",84),u(4,"$"),d(),l(5,"code",85),u(6,"artemis init"),d(),l(7,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard("artemis init","cfg_init"))}),l(8,"span",29),u(9),d(),l(10,"span"),u(11),d()()()()()}if(n&2){let e=m(2);g(7),w("copied",e.copiedId()==="cfg_init"),g(2),O(e.copiedId()==="cfg_init"?"check":"content_copy"),g(2),O(e.copiedId()==="cfg_init"?"Copied!":"Copy")}}function ED(n,t){if(n&1){let e=L();l(0,"div",42)(1,"div",83)(2,"div",81)(3,"span",84),u(4,"$"),d(),l(5,"code",85),u(6),d(),l(7,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard(i.toolchainInstallCmd(),"install_toolchain"))}),l(8,"span",29),u(9),d(),l(10,"span"),u(11),d()()()()()}if(n&2){let e=m(2);g(6),O(e.toolchainInstallCmd()),g(),w("copied",e.copiedId()==="install_toolchain"),g(2),O(e.copiedId()==="install_toolchain"?"check":"content_copy"),g(2),O(e.copiedId()==="install_toolchain"?"Copied!":"Copy")}}function DD(n,t){n&1&&(l(0,"span",23),u(1,"check"),d())}function ID(n,t){n&1&&(l(0,"span"),u(1,"2"),d())}function TD(n,t){if(n&1){let e=L();l(0,"button",111),k("click",function(){v(e);let i=m(3);return y(i.copyToClipboard(i.geminiKeyInput(),"gemini_key_field"))}),l(1,"span",29),u(2),d()()}if(n&2){let e=m(3);g(),w("text-emerald",e.copiedId()==="gemini_key_field"),g(),V(" ",e.copiedId()==="gemini_key_field"?"check":"content_copy"," ")}}function AD(n,t){if(n&1&&(l(0,"span",112),u(1,"sync"),d(),l(2,"span"),u(3),d()),n&2){let e=m(3);g(3),O(e.geminiKeyInput().trim()?"Verifying & Saving...":"Clearing...")}}function ND(n,t){if(n&1&&(l(0,"span",29),u(1),d(),l(2,"span"),u(3),d()),n&2){let e=m(3);g(),O(e.geminiKeyInput().trim()?"save":"delete_outline"),g(2),O(e.geminiKeyInput().trim()?e.isGeminiModified()?"Save & Apply":"Save Key":"Clear Key")}}function RD(n,t){n&1&&(l(0,"span",112),u(1,"sync"),d(),l(2,"span"),u(3,"Testing..."),d())}function FD(n,t){n&1&&(l(0,"span",29),u(1,"network_check"),d(),l(2,"span"),u(3,"Test Key"),d())}function LD(n,t){if(n&1&&(l(0,"div",99)(1,"span",29),u(2,"check_circle"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(3);g(4),O(e.geminiSaveMessage())}}function VD(n,t){if(n&1&&(l(0,"div",100)(1,"span",29),u(2,"error"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(3);g(4),O(e.geminiSaveError())}}function jD(n,t){n&1&&$(0,"span",108)}function $D(n,t){if(n&1){let e=L();l(0,"button",111),k("click",function(){v(e);let i=m(4);return y(i.copyToClipboard(i.ocrKeyInput(),"ocr_key_field"))}),l(1,"span",29),u(2),d()()}if(n&2){let e=m(4);g(),w("text-emerald",e.copiedId()==="ocr_key_field"),g(),V(" ",e.copiedId()==="ocr_key_field"?"check":"content_copy"," ")}}function zD(n,t){if(n&1&&(l(0,"span",112),u(1,"sync"),d(),l(2,"span"),u(3),d()),n&2){let e=m(4);g(3),O(e.ocrKeyInput().trim()?"Verifying & Saving...":"Clearing...")}}function BD(n,t){if(n&1&&(l(0,"span",29),u(1),d(),l(2,"span"),u(3),d()),n&2){let e=m(4);g(),O(e.ocrKeyInput().trim()?"save":"delete_outline"),g(2),O(e.ocrKeyInput().trim()?e.isOcrModified()?"Save & Apply":"Save Key":"Clear Key")}}function HD(n,t){n&1&&(l(0,"span",112),u(1,"sync"),d(),l(2,"span"),u(3,"Testing..."),d())}function UD(n,t){n&1&&(l(0,"span",29),u(1,"network_check"),d(),l(2,"span"),u(3,"Test Key"),d())}function GD(n,t){if(n&1&&(l(0,"div",99)(1,"span",29),u(2,"check_circle"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.ocrSaveMessage())}}function qD(n,t){if(n&1&&(l(0,"div",100)(1,"span",29),u(2,"error"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.ocrSaveError())}}function WD(n,t){if(n&1){let e=L();l(0,"div",110)(1,"p",113),u(2," Provides screen text coordinate grounding to improve element detection and interaction accuracy on text-heavy apps. "),d(),l(3,"div",114)(4,"div",87)(5,"div",88)(6,"span",89),u(7,"key"),d(),l(8,"span"),u(9,"Cloud Vision API Key:"),d()()(),l(10,"div",91)(11,"div",92)(12,"span",93),u(13,"key"),d(),l(14,"input",115),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.onOcrKeyChange(i))}),d(),Rt(),l(15,"button",95),k("click",function(){v(e);let i=m(3);return y(i.toggleOcrKeyVisibility())}),l(16,"span",29),u(17),d()(),M(18,$D,3,3,"button",96),d(),l(19,"button",97),k("click",function(){v(e);let i=m(3);return y(i.saveOcrKey())}),M(20,zD,4,1)(21,BD,4,2),d(),l(22,"button",116),k("click",function(){v(e);let i=m(3);return y(i.testOcrKey())}),M(23,HD,4,0)(24,UD,4,0),d()(),M(25,GD,5,1,"div",99),M(26,qD,5,1,"div",100),d()()}if(n&2){let e=m(3);g(14),w("is-masked",!e.showOcrKey()),R("type",e.showOcrKey()?"text":"password")("ngModel",e.ocrKeyInput()),Ft(),g(),R("title",e.showOcrKey()?"Mask Key":"Show Plaintext"),g(2),V(" ",e.showOcrKey()?"visibility_off":"visibility"," "),g(),b(e.ocrKeyInput()?18:-1),g(),w("btn-dirty",e.isOcrModified()),R("disabled",e.isSavingOcrKey()||e.isTestingOcrKey()||!e.isOcrModified()&&!e.ocrKeyInput().trim()),g(),b(e.isSavingOcrKey()?20:21),g(2),R("disabled",e.isTestingOcrKey()||e.isSavingOcrKey()||!e.ocrKeyInput().trim()),g(),b(e.isTestingOcrKey()?23:24),g(2),b(e.ocrSaveMessage()?25:-1),g(),b(e.ocrSaveError()?26:-1)}}function KD(n,t){if(n&1){let e=L();l(0,"div",86)(1,"div",87)(2,"div",88)(3,"span",89),u(4,"vpn_key"),d(),l(5,"span"),u(6,"Enter Gemini API Key:"),d()(),l(7,"a",90)(8,"span",29),u(9,"open_in_new"),d(),l(10,"span"),u(11,"Get Free API Key (Google AI Studio)"),d()()(),l(12,"div",91)(13,"div",92)(14,"span",93),u(15,"key"),d(),l(16,"input",94),k("ngModelChange",function(i){v(e);let r=m(2);return y(r.onGeminiKeyChange(i))}),d(),Rt(),l(17,"button",95),k("click",function(){v(e);let i=m(2);return y(i.toggleGeminiKeyVisibility())}),l(18,"span",29),u(19),d()(),M(20,TD,3,3,"button",96),d(),l(21,"button",97),k("click",function(){v(e);let i=m(2);return y(i.saveGeminiKey())}),M(22,AD,4,1)(23,ND,4,2),d(),l(24,"button",98),k("click",function(){v(e);let i=m(2);return y(i.testGeminiKey())}),M(25,RD,4,0)(26,FD,4,0),d()(),M(27,LD,5,1,"div",99),M(28,VD,5,1,"div",100),d(),l(29,"div",101)(30,"div",102),k("click",function(){v(e);let i=m(2);return y(i.toggleOcrConfig())}),l(31,"div",103)(32,"span",104),u(33,"document_scanner"),d(),l(34,"div",105)(35,"span",106),u(36,"Optional: Google Cloud Vision OCR"),d()()(),l(37,"div",107),M(38,jD,1,0,"span",108),l(39,"span",109),u(40),d()()(),M(41,WD,27,15,"div",110),d()}if(n&2){let e=m(2);g(16),w("is-masked",!e.showGeminiKey()),R("type",e.showGeminiKey()?"text":"password")("ngModel",e.geminiKeyInput()),Ft(),g(),R("title",e.showGeminiKey()?"Mask Key":"Show Plaintext"),g(2),V(" ",e.showGeminiKey()?"visibility_off":"visibility"," "),g(),b(e.geminiKeyInput()?20:-1),g(),w("btn-dirty",e.isGeminiModified()),R("disabled",e.isSavingGeminiKey()||e.isTestingGeminiKey()||!e.isGeminiModified()&&!e.geminiKeyInput().trim()),g(),b(e.isSavingGeminiKey()?22:23),g(2),R("disabled",e.isTestingGeminiKey()||e.isSavingGeminiKey()||!e.geminiKeyInput().trim()),g(),b(e.isTestingGeminiKey()?25:26),g(2),b(e.geminiSaveMessage()?27:-1),g(),b(e.geminiSaveError()?28:-1),g(10),b(e.isOcrConfigured()?38:-1),g(2),V(" ",e.showOcrConfig()?"expand_less":"expand_more"," "),g(),b(e.showOcrConfig()?41:-1)}}function YD(n,t){if(n&1&&(l(0,"div",128)(1,"span",129),u(2,"Fallback Model"),d(),l(3,"div",138)(4,"span"),u(5),d()()()),n&2){let e=m(3);g(5),We("",e.modelConfigEnv()?.default_model?.fallback?.provider,"/",e.modelConfigEnv()?.default_model?.fallback?.model)}}function ZD(n,t){if(n&1&&(l(0,"div",135)(1,"pre",139)(2,"code"),u(3),d()()()),n&2){let e=m(3);g(3),O(e.modelConfigEnv()?.config_content)}}function QD(n,t){if(n&1&&(l(0,"code",146),u(1),d()),n&2){let e=m(2).$implicit;g(),V("(",e.preview,")")}}function JD(n,t){if(n&1&&(l(0,"span",144)(1,"span",29),u(2,"check_circle"),d(),l(3,"span"),u(4,"Configured"),d(),M(5,QD,2,1,"code",146),d()),n&2){let e=m().$implicit;g(5),b(e.preview?5:-1)}}function XD(n,t){n&1&&(l(0,"span",145)(1,"span",29),u(2,"radio_button_unchecked"),d(),l(3,"span"),u(4,"Not Set"),d()())}function eI(n,t){if(n&1&&(l(0,"div",140)(1,"div",141)(2,"code",142),u(3),d()(),l(4,"div",143),M(5,JD,6,1,"span",144)(6,XD,5,0,"span",145),d()()),n&2){let e=t.$implicit;w("is-configured",e.is_set),g(3),O(e.name),g(2),b(e.is_set?5:6)}}function tI(n,t){if(n&1){let e=L();l(0,"div",61)(1,"div",117)(2,"div",118)(3,"div",119)(4,"span",120),u(5,"tune"),d(),l(6,"div",121)(7,"span",122),u(8,"Active Model Configuration"),d(),l(9,"span",123),u(10,"Defined in "),l(11,"code"),u(12),d()()()(),l(13,"div",124)(14,"button",125),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard(i.modelConfigEnv()?.config_path||"config/artemis.jsonc","config_path_btn"))}),l(15,"span",29),u(16),d(),l(17,"span"),u(18,"Copy Path"),d()(),l(19,"button",126),k("click",function(){v(e);let i=m(2);return y(i.toggleFullConfigFile())}),l(20,"span",29),u(21),d(),l(22,"span"),u(23),d()()()(),l(24,"div",127)(25,"div",128)(26,"span",129),u(27,"Default Model"),d(),l(28,"div",130),$(29,"span",131),l(30,"span",132),u(31),d(),l(32,"span",133),u(33,"/"),d(),l(34,"span",134),u(35),d()()(),M(36,YD,6,2,"div",128),d(),M(37,ZD,4,1,"div",135),d(),l(38,"div",117)(39,"div",118)(40,"div",119)(41,"span",120),u(42,"vpn_key"),d(),l(43,"div",121)(44,"span",122),u(45,"Environment Variables & API Keys"),d(),l(46,"span",123),u(47,"Loaded from "),l(48,"code"),u(49),d()()()(),l(50,"button",125),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard(i.modelConfigEnv()?.env_path||".env","env_path_btn"))}),l(51,"span",29),u(52),d(),l(53,"span"),u(54,"Copy Path"),d()()(),l(55,"div",136),G(56,eI,7,4,"div",137,GP),d()()()}if(n&2){let e=m(2);g(12),O(e.modelConfigEnv()?.config_path||"config/artemis.jsonc"),g(3),w("text-emerald",e.copiedId()==="config_path_btn"),g(),V(" ",e.copiedId()==="config_path_btn"?"check":"content_copy"," "),g(5),V(" ",e.showFullConfigFile()?"unfold_less":"code"," "),g(2),O(e.showFullConfigFile()?"Hide JSONC":"View JSONC"),g(8),O(e.modelConfigEnv()?.default_model?.provider||"google"),g(4),O(e.modelConfigEnv()?.default_model?.model||"gemini-3.8-flash"),g(),b(e.modelConfigEnv()?.default_model?.fallback?36:-1),g(),b(e.showFullConfigFile()?37:-1),g(12),O(e.modelConfigEnv()?.env_path||".env"),g(2),w("text-emerald",e.copiedId()==="env_path_btn"),g(),V(" ",e.copiedId()==="env_path_btn"?"check":"content_copy"," "),g(4),q(e.modelConfigEnv()?.env_vars||za(14,hD))}}function nI(n,t){n&1&&(l(0,"span",23),u(1,"check"),d())}function oI(n,t){n&1&&(l(0,"span"),u(1,"3"),d())}function iI(n,t){n&1&&(l(0,"span",147),u(1,"sync"),d(),l(2,"span"),u(3,"Restarting..."),d())}function rI(n,t){if(n&1&&(l(0,"span",148),u(1,"check"),d(),l(2,"span",149),u(3),d()),n&2){let e=m(2);g(3),O(e.adbRestartFeedback())}}function aI(n,t){if(n&1&&(l(0,"span",29),u(1,"sync"),d(),l(2,"span"),u(3),d()),n&2){let e=m(2);g(3),O(e.isRemoteAdbServer()?"Refresh Devices":"Restart ADB")}}function sI(n,t){n&1&&(l(0,"span"),u(1,"Virtual Device"),d(),l(2,"span",160),u(3,"\xB7"),d())}function cI(n,t){if(n&1&&(l(0,"span",160),u(1,"\xB7"),d(),l(2,"span"),u(3),d()),n&2){let e=m(3);g(3),O(e.activeDevice()?.screen_resolution)}}function lI(n,t){if(n&1&&(l(0,"span",160),u(1,"\xB7"),d(),l(2,"span"),u(3),d()),n&2){let e=m(3);g(3),V("Android ",e.activeDevice()?.android_version)}}function dI(n,t){if(n&1&&(l(0,"span",160),u(1,"\xB7"),d(),l(2,"span",161),u(3),d()),n&2){let e=m(3);g(3),We(" Remote via ",e.adbServerStatus()?.endpoint?.host,":",e.adbServerStatus()?.endpoint?.port," ")}}function uI(n,t){n&1&&(l(0,"span",167),u(1,"\u2713 Active"),d())}function gI(n,t){if(n&1){let e=L();l(0,"button",165),k("click",function(){let i=v(e).$implicit,r=m(4);return y(r.selectTargetDevice(i.serial))}),l(1,"span",166),u(2," phone_android "),d(),l(3,"span"),u(4),d(),M(5,uI,2,0,"span",167),d()}if(n&2){let e=t.$implicit,o=m(4);w("selected",e.serial===o.activeDevice()?.serial),g(4),O(e.model||e.serial),g(),b(e.serial===o.activeDevice()?.serial?5:-1)}}function pI(n,t){if(n&1&&(l(0,"div",159)(1,"span",162),u(2,"Select Active Device:"),d(),l(3,"div",163),G(4,gI,6,4,"button",164,Pm),d()()),n&2){let e=m(3);g(4),q(e.connectedDevices())}}function fI(n,t){if(n&1&&(l(0,"div",64)(1,"div",150)(2,"div",151)(3,"span",152),u(4," phone_android "),d()(),l(5,"div",153)(6,"div",154),u(7),d(),l(8,"div",155),M(9,sI,4,0),l(10,"span"),u(11),d(),M(12,cI,4,1),M(13,lI,4,1),M(14,dI,4,2),d()(),l(15,"div",156),$(16,"span",157),l(17,"span",158),u(18,"Connected"),d()()(),M(19,pI,6,0,"div",159),d()),n&2){let e=m(2);g(7),V(" ",e.activeDevice()?.model||e.activeDevice()?.serial," "),g(2),b(e.activeDevice()?.is_emulator?9:-1),g(2),O(e.activeDevice()?.serial),g(),b(e.activeDevice()?.screen_resolution?12:-1),g(),b(e.activeDevice()?.android_version?13:-1),g(),b(e.isRemoteAdbServer()?14:-1),g(5),b(e.connectedDevices().length>1?19:-1)}}function mI(n,t){n&1&&(l(0,"span",185),u(1,"check"),d())}function _I(n,t){n&1&&$(0,"span",186)}function hI(n,t){n&1&&(l(0,"span",185),u(1,"check"),d())}function CI(n,t){n&1&&$(0,"span",186)}function MI(n,t){n&1&&(l(0,"span"),u(1,"2"),d())}function bI(n,t){n&1&&(l(0,"span",185),u(1,"check"),d())}function PI(n,t){n&1&&$(0,"span",186)}function OI(n,t){n&1&&(l(0,"span"),u(1,"3"),d())}function vI(n,t){if(n&1&&(l(0,"div",204),u(1),d()),n&2){let e=t.$implicit;g(),O(e)}}function yI(n,t){if(n&1&&G(0,vI,2,1,"div",204,Ie),n&2){let e=m(4);q(e.emulatorLaunchState()?.logs)}}function xI(n,t){n&1&&(l(0,"div",203),u(1,"Waiting for process output..."),d())}function wI(n,t){if(n&1){let e=L();l(0,"div",198)(1,"div",199)(2,"div",200)(3,"span",29),u(4,"terminal"),d(),l(5,"span"),u(6,"Emulator Console Stream (stdout / stderr)"),d()(),l(7,"button",201),k("click",function(){v(e);let i=m(3);return y(i.copyToClipboard(i.emulatorLaunchState()?.logs?.join(` +`)||"","boot_logs"))}),l(8,"span",29),u(9),d(),l(10,"span"),u(11),d()()(),l(12,"pre",202),M(13,yI,2,0)(14,xI,2,0,"div",203),d()()}if(n&2){let e=m(3);g(9),O(e.copiedId()==="boot_logs"?"check":"content_copy"),g(2),O(e.copiedId()==="boot_logs"?"Copied":"Copy Logs"),g(2),b(e.emulatorLaunchState()?.logs?.length?13:14)}}function SI(n,t){if(n&1){let e=L();l(0,"div",65)(1,"div",168)(2,"div",169)(3,"div",170)(4,"div",171)(5,"span",172),u(6,"sync"),d()(),l(7,"div",173)(8,"div",174)(9,"h4",175),u(10," Launching Android Emulator: "),l(11,"span",176),u(12),d()(),l(13,"span",177),u(14),d()(),l(15,"p",178),u(16),d()()(),l(17,"div",179)(18,"button",180),k("click",function(){v(e);let i=m(2);return y(i.toggleLaunchLogs())}),l(19,"span",29),u(20,"terminal"),d(),l(21,"span"),u(22),d()(),l(23,"button",181),k("click",function(){v(e);let i=m(2);return y(i.stopEmulator())}),l(24,"span",29),u(25,"cancel"),d(),l(26,"span"),u(27,"Stop"),d()()()(),l(28,"div",182)(29,"div",183)(30,"div",184),M(31,mI,2,0,"span",185)(32,_I,1,0,"span",186),d(),l(33,"span",187),u(34,"1. Process Start"),d()(),$(35,"div",188),l(36,"div",183)(37,"div",184),M(38,hI,2,0,"span",185)(39,CI,1,0,"span",186)(40,MI,2,0,"span"),d(),l(41,"span",187),u(42,"2. ADB Handshake"),d()(),$(43,"div",188),l(44,"div",183)(45,"div",184),M(46,bI,2,0,"span",185)(47,PI,1,0,"span",186)(48,OI,2,0,"span"),d(),l(49,"span",187),u(50,"3. Android OS Boot"),d()(),$(51,"div",188),l(52,"div",183)(53,"div",184)(54,"span",185),u(55,"check_circle"),d()(),l(56,"span",187),u(57,"4. Ready"),d()()(),l(58,"div",189)(59,"div",190),$(60,"div",191),d(),l(61,"div",192)(62,"span",193),u(63),d(),l(64,"span",194)(65,"span",195),u(66,"timer"),d(),u(67),d()()(),l(68,"div",196)(69,"span",197),u(70,"info"),d(),l(71,"span"),u(72,"Android virtualization cold boot usually takes 20-45 seconds. Live progress is actively tracked above\u2014please do not click launch repeatedly."),d()(),M(73,wI,15,3,"div",198),d()()}if(n&2){let e=m(2);g(12),O(e.emulatorLaunchState()?.avd_name||e.activeDevice()?.serial||"Virtual Device"),g(2),O(e.emulatorLaunchState()?.status==="booting"?"OS Booting":"Starting"),g(2),O(e.emulatorLaunchState()?.stage_message||"Initializing Android Virtual Device..."),g(2),w("active",e.showLaunchLogs()),g(4),O(e.showLaunchLogs()?"Hide Logs":"View Boot Logs"),g(7),w("done",e.emulatorLaunchState()?.progress_percent>=35)("active",e.emulatorLaunchState()?.status==="starting"),g(2),b(e.emulatorLaunchState()?.progress_percent>=35?31:32),g(4),w("done",e.emulatorLaunchState()?.progress_percent>=35),g(),w("done",e.emulatorLaunchState()?.progress_percent>=65)("active",e.emulatorLaunchState()?.status==="waiting_for_adb"),g(2),b(e.emulatorLaunchState()?.progress_percent>=65?38:e.emulatorLaunchState()?.status==="waiting_for_adb"?39:40),g(5),w("done",e.emulatorLaunchState()?.progress_percent>=65),g(),w("done",e.emulatorLaunchState()?.status==="ready")("active",e.emulatorLaunchState()?.status==="booting"||e.adbProbe()?.summary==="Device Booting"),g(2),b(e.emulatorLaunchState()?.status==="ready"?46:e.emulatorLaunchState()?.status==="booting"||e.adbProbe()?.summary==="Device Booting"?47:48),g(5),w("done",e.emulatorLaunchState()?.status==="ready"),g(),w("done",e.emulatorLaunchState()?.status==="ready"),g(8),Se("width",e.emulatorLaunchState()?.progress_percent||25,"%"),g(3),V("",e.emulatorLaunchState()?.progress_percent||25,"% Complete"),g(4),V(" Elapsed: ",e.emulatorLaunchState()?.elapsed_seconds||0,"s "),g(6),b(e.showLaunchLogs()?73:-1)}}function kI(n,t){if(n&1&&(l(0,"div",204),u(1),d()),n&2){let e=t.$implicit;g(),O(e)}}function EI(n,t){if(n&1){let e=L();l(0,"div",214)(1,"div",199)(2,"span"),u(3,"Process Output / Failure Logs:"),d(),l(4,"button",201),k("click",function(){v(e);let i=m(3);return y(i.copyToClipboard(i.emulatorLaunchState()?.logs?.join(` +`)||"","fail_logs"))}),l(5,"span",29),u(6),d(),l(7,"span"),u(8),d()()(),l(9,"pre",219),G(10,kI,2,1,"div",204,Ie),d()()}if(n&2){let e=m(3);g(6),O(e.copiedId()==="fail_logs"?"check":"content_copy"),g(2),O(e.copiedId()==="fail_logs"?"Copied":"Copy Logs"),g(2),q(e.emulatorLaunchState()?.logs)}}function DI(n,t){if(n&1){let e=L();l(0,"button",220),k("click",function(){v(e);let i=m(3);return y(i.launchAvdEmulator(i.emulatorLaunchState().avd_name))}),l(1,"span",29),u(2,"refresh"),d(),l(3,"span"),u(4),d()()}if(n&2){let e=m(3);R("disabled",e.isRemoteAdbServer()),g(4),V("Retry Launch (",e.emulatorLaunchState()?.avd_name,")")}}function II(n,t){if(n&1){let e=L();l(0,"div",66)(1,"div",205)(2,"div",206)(3,"div",207)(4,"div",208)(5,"span",209),u(6,"error"),d()(),l(7,"div",210)(8,"h4",211),u(9,"Emulator Launch Failed"),d(),l(10,"p",212),u(11),d()()(),l(12,"button",213),k("click",function(){v(e);let i=m(2);return y(i.dismissEmulatorStatus())}),l(13,"span",29),u(14,"close"),d()()(),M(15,EI,12,2,"div",214),l(16,"div",215),M(17,DI,5,2,"button",216),l(18,"button",217),k("click",function(){v(e);let i=m(2);return y(i.restartAdbServer())}),l(19,"span",29),u(20,"sync"),d(),l(21,"span"),u(22,"Restart ADB Server"),d()(),l(23,"button",218),k("click",function(){v(e);let i=m(2);return y(i.dismissEmulatorStatus())}),l(24,"span"),u(25,"Dismiss"),d()()()()()}if(n&2){let e=m(2);g(11),O(e.emulatorLaunchState()?.error||"The Android emulator process failed to start or crashed during initialization."),g(4),b(e.emulatorLaunchState()?.logs?.length?15:-1),g(2),b(e.emulatorLaunchState()?.avd_name?17:-1)}}function TI(n,t){if(n&1&&u(0),n&2){let e=m(3);We(" ",e.installedAvds().length," local virtual device",e.installedAvds().length>1?"s":""," available ")}}function AI(n,t){n&1&&u(0," Launch a local Android Virtual Device ")}function NI(n,t){if(n&1){let e=L();l(0,"div",231)(1,"div",234)(2,"span",29),u(3,"hub"),d(),l(4,"span"),u(5," Artemis is currently using "),l(6,"strong"),u(7),d(),u(8,". Switch to local ADB before using a local emulator, USB cable, or Wireless ADB. "),d()(),l(9,"button",235),k("click",function(){v(e);let i=m(3);return y(i.switchToLocalAdbServer())}),l(10,"span",29),u(11,"sync"),d(),l(12,"span"),u(13),d()()()}if(n&2){let e=m(3);g(7),We("",e.adbServerStatus()?.endpoint?.host,":",e.adbServerStatus()?.endpoint?.port),g(2),R("disabled",e.isSwitchingToLocalAdb()),g(),w("spinning",e.isSwitchingToLocalAdb()),g(3),O(e.isSwitchingToLocalAdb()?"Switching...":"Use Local ADB")}}function RI(n,t){if(n&1&&($(0,"span",252),l(1,"span"),u(2),d()),n&2){let e=m(6);g(2),We("",e.emulatorLaunchState()?.status==="booting"?"Booting OS":"Starting"," (",e.emulatorLaunchState()?.elapsed_seconds||0,"s)")}}function FI(n,t){n&1&&(l(0,"span",253),u(1,"play_arrow"),d(),l(2,"span"),u(3,"Launch"),d())}function LI(n,t){if(n&1){let e=L();l(0,"div",243)(1,"div",244)(2,"div",245)(3,"span",29),u(4,"smartphone"),d()(),l(5,"div",246)(6,"div",247)(7,"span",248),u(8),d(),l(9,"span",249),u(10,"Virtual Device"),d()()()(),l(11,"div",250)(12,"button",251),k("click",function(){let i=v(e).$implicit,r=m(5);return y(r.launchAvdEmulator(i))}),M(13,RI,3,2)(14,FI,4,0),d()()()}if(n&2){let e=t.$implicit,o=m(5);g(8),O(e),g(4),w("launching",o.launchingAvd()===e||o.isEmulatorLaunching()&&o.emulatorLaunchState()?.avd_name===e),R("disabled",o.isEmulatorLaunching()||o.isRemoteAdbServer())("title",o.isEmulatorLaunching()?"An emulator is currently launching...":"Launch "+e),g(),b(o.launchingAvd()===e||o.isEmulatorLaunching()&&o.emulatorLaunchState()?.avd_name===e?13:14)}}function VI(n,t){if(n&1&&(l(0,"div",236)(1,"div",238)(2,"div",239)(3,"span",120),u(4,"devices"),d(),l(5,"span",240),u(6,"Installed Virtual Devices (AVD)"),d(),l(7,"span",241),u(8),d()()(),l(9,"div",242),G(10,LI,15,6,"div",243,Ie),d()()),n&2){let e=m(4);g(8),V("",e.installedAvds().length," Available"),g(2),q(e.installedAvds())}}function jI(n,t){if(n&1&&(l(0,"div",264)(1,"div",266)(2,"div",267)(3,"span",268),u(4,"1"),d(),l(5,"span",269),u(6,"download"),d()(),l(7,"div",270),u(8,"Install Android Studio"),d(),l(9,"div",271),u(10),l(11,"a",272),u(12,"developer.android.com/studio"),d(),u(13," and install. "),d()(),l(14,"div",266)(15,"div",267)(16,"span",268),u(17,"2"),d(),l(18,"span",269),u(19,"developer_mode"),d()(),l(20,"div",270),u(21,"Open Device Manager"),d(),l(22,"div",271),u(23," On Studio welcome screen, open "),l(24,"strong"),u(25,"More Actions > Virtual Device Manager"),d(),u(26," (or "),l(27,"strong"),u(28,"Tools > Device Manager"),d(),u(29,"). "),d()(),l(30,"div",266)(31,"div",267)(32,"span",268),u(33,"3"),d(),l(34,"span",269),u(35,"add_circle"),d()(),l(36,"div",270),u(37,"Create Virtual Device"),d(),l(38,"div",271),u(39," Click "),l(40,"strong"),u(41,"Create Device"),d(),u(42,", choose "),l(43,"strong"),u(44,"Pixel 8"),d(),u(45,", select "),l(46,"strong"),u(47,"Android 14 (API 34)"),d(),u(48,", and click "),l(49,"strong"),u(50,"Finish"),d(),u(51,". "),d()(),l(52,"div",266)(53,"div",267)(54,"span",268),u(55,"4"),d(),l(56,"span",269),u(57,"play_arrow"),d()(),l(58,"div",270),u(59,"Launch Emulator"),d(),l(60,"div",271),u(61," Click \u25B6 "),l(62,"strong"),u(63,"Play"),d(),u(64," in Device Manager. Artemis will detect and connect to it automatically! "),d()()()),n&2){let e=m(5);g(10),V(" Download for ",e.effectiveOs()==="darwin"?"macOS":e.effectiveOs()==="windows"?"Windows":"Linux"," from ")}}function $I(n,t){if(n&1){let e=L();l(0,"div",265)(1,"div",273)(2,"div",274),u(3,"1"),d(),l(4,"div",275)(5,"div",26),u(6),d(),l(7,"p",276),u(8),d(),l(9,"div",81)(10,"span",84),u(11),d(),l(12,"code",85),u(13),d(),l(14,"button",82),k("click",function(){v(e);let i=m(5);return y(i.copyToClipboard(i.emuHypervisorCmd(),"emu_kvm"))}),l(15,"span",29),u(16),d(),l(17,"span"),u(18),d()()()()(),l(19,"div",273)(20,"div",274),u(21,"2"),d(),l(22,"div",275)(23,"div",26),u(24,"Download Emulator & System Image (API 34)"),d(),l(25,"p",276),u(26,"Install Android 14 Google APIs system image using SDK Manager:"),d(),l(27,"div",81)(28,"span",84),u(29),d(),l(30,"code",85),u(31),d(),l(32,"button",82),k("click",function(){v(e);let i=m(5);return y(i.copyToClipboard(i.emuSdkInstallCmd(),"emu_sdk"))}),l(33,"span",29),u(34),d(),l(35,"span"),u(36),d()()()()(),l(37,"div",273)(38,"div",274),u(39,"3"),d(),l(40,"div",275)(41,"div",26),u(42,"Create Virtual Device (Pixel 8 AVD)"),d(),l(43,"p",276),u(44,"Initialize a new Android Virtual Device named "),l(45,"code"),u(46,"Pixel_8_API_34"),d(),u(47,":"),d(),l(48,"div",81)(49,"span",84),u(50),d(),l(51,"code",85),u(52),d(),l(53,"button",82),k("click",function(){v(e);let i=m(5);return y(i.copyToClipboard(i.emuCreateAvdCmd(),"emu_create"))}),l(54,"span",29),u(55),d(),l(56,"span"),u(57),d()()()()(),l(58,"div",273)(59,"div",274),u(60,"4"),d(),l(61,"div",275)(62,"div",26),u(63,"Start the Emulator"),d(),l(64,"p",276),u(65,"Launch the virtual device (or add "),l(66,"code"),u(67,"-no-window"),d(),u(68," for headless execution):"),d(),l(69,"div",81)(70,"span",84),u(71),d(),l(72,"code",85),u(73,"emulator -avd Pixel_8_API_34 -no-boot-anim"),d(),l(74,"button",82),k("click",function(){v(e);let i=m(5);return y(i.copyToClipboard("emulator -avd Pixel_8_API_34 -no-boot-anim","emu_run"))}),l(75,"span",29),u(76),d(),l(77,"span"),u(78),d()()()()()()}if(n&2){let e=m(5);g(6),O(e.emuHypervisorTitle()),g(2),O(e.emuHypervisorDesc()),g(3),O(e.effectiveOs()==="windows"?"PS>":"$"),g(2),O(e.emuHypervisorCmd()),g(),w("copied",e.copiedId()==="emu_kvm"),g(2),O(e.copiedId()==="emu_kvm"?"check":"content_copy"),g(2),O(e.copiedId()==="emu_kvm"?"Copied!":"Copy"),g(11),O(e.effectiveOs()==="windows"?"PS>":"$"),g(2),O(e.emuSdkInstallCmd()),g(),w("copied",e.copiedId()==="emu_sdk"),g(2),O(e.copiedId()==="emu_sdk"?"check":"content_copy"),g(2),O(e.copiedId()==="emu_sdk"?"Copied!":"Copy"),g(14),O(e.effectiveOs()==="windows"?"PS>":"$"),g(2),O(e.emuCreateAvdCmd()),g(),w("copied",e.copiedId()==="emu_create"),g(2),O(e.copiedId()==="emu_create"?"check":"content_copy"),g(2),O(e.copiedId()==="emu_create"?"Copied!":"Copy"),g(14),O(e.effectiveOs()==="windows"?"PS>":"$"),g(3),w("copied",e.copiedId()==="emu_run"),g(2),O(e.copiedId()==="emu_run"?"check":"content_copy"),g(2),O(e.copiedId()==="emu_run"?"Copied!":"Copy")}}function zI(n,t){if(n&1){let e=L();l(0,"div",237)(1,"div",254)(2,"div",255)(3,"div",256)(4,"span",29),u(5,"devices_off"),d()(),l(6,"div",257)(7,"div",258)(8,"h4",259),u(9,"No Android Virtual Devices (AVD) Detected"),d(),l(10,"div",78)(11,"button",79),k("click",function(){v(e);let i=m(4);return y(i.setSelectedOs("linux"))}),u(12,"Linux"),d(),l(13,"button",79),k("click",function(){v(e);let i=m(4);return y(i.setSelectedOs("darwin"))}),u(14,"macOS"),d(),l(15,"button",79),k("click",function(){v(e);let i=m(4);return y(i.setSelectedOs("windows"))}),u(16,"Windows"),d()()(),l(17,"p",260),u(18,"Follow the step-by-step instructions below to install and start an emulator:"),d()()(),l(19,"div",261)(20,"button",262),k("click",function(){v(e);let i=m(4);return y(i.setEmulatorSetupMode("studio"))}),l(21,"span",263),u(22,"desktop_windows"),d(),l(23,"span"),u(24,"Android Studio (GUI)"),d()(),l(25,"button",262),k("click",function(){v(e);let i=m(4);return y(i.setEmulatorSetupMode("cli"))}),l(26,"span",263),u(27,"terminal"),d(),l(28,"span"),u(29,"Command Line (CLI)"),d()()()(),M(30,jI,65,1,"div",264),M(31,$I,79,25,"div",265),d()}if(n&2){let e=m(4);g(11),w("active",e.effectiveOs()==="linux"),g(2),w("active",e.effectiveOs()==="darwin"),g(2),w("active",e.effectiveOs()==="windows"),g(5),w("active",e.emulatorSetupMode()==="studio"),g(5),w("active",e.emulatorSetupMode()==="cli"),g(5),b(e.emulatorSetupMode()==="studio"?30:-1),g(),b(e.emulatorSetupMode()==="cli"?31:-1)}}function BI(n,t){if(n&1&&(l(0,"div",232),M(1,VI,12,1,"div",236)(2,zI,32,12,"div",237),d()),n&2){let e=m(3);g(),b(e.installedAvds().length>0?1:2)}}function HI(n,t){n&1&&(l(0,"div",232)(1,"div",277)(2,"span",278),u(3,"cable"),d(),l(4,"span"),u(5,"Connect your Android phone via USB cable and enable USB Debugging:"),d()(),l(6,"div",279)(7,"div",266)(8,"div",267)(9,"span",268),u(10,"1"),d(),l(11,"span",269),u(12,"usb"),d()(),l(13,"div",270),u(14,"Connect USB Cable"),d(),l(15,"div",271),u(16,"Plug your Android phone into this computer with a data cable."),d()(),l(17,"div",266)(18,"div",267)(19,"span",268),u(20,"2"),d(),l(21,"span",269),u(22,"touch_app"),d()(),l(23,"div",270),u(24,"Enable Developer Options"),d(),l(25,"div",271),u(26,"In phone "),l(27,"strong"),u(28,"Settings > About Phone"),d(),u(29,", tap "),l(30,"strong"),u(31,"Build Number"),d(),u(32," 7 times."),d()(),l(33,"div",266)(34,"div",267)(35,"span",268),u(36,"3"),d(),l(37,"span",269),u(38,"toggle_on"),d()(),l(39,"div",270),u(40,"Enable USB Debugging"),d(),l(41,"div",271),u(42,"In "),l(43,"strong"),u(44,"Settings > Developer Options"),d(),u(45,", turn "),l(46,"strong"),u(47,"USB Debugging"),d(),u(48," ON. "),d()(),l(49,"div",266)(50,"div",267)(51,"span",268),u(52,"4"),d(),l(53,"span",269),u(54,"verified_user"),d()(),l(55,"div",270),u(56,"Authorize Computer"),d(),l(57,"div",271),u(58,"Check "),l(59,"strong"),u(60,'"Always allow"'),d(),u(61," and tap "),l(62,"strong"),u(63,"Allow"),d(),u(64," on the phone prompt. "),d()()()())}function UI(n,t){n&1&&(l(0,"span",147),u(1,"sync"),d(),l(2,"span"),u(3,"Connecting..."),d())}function GI(n,t){n&1&&(l(0,"span",29),u(1,"bolt"),d(),l(2,"span"),u(3,"Connect"),d())}function qI(n,t){if(n&1&&(l(0,"div",292)(1,"span",29),u(2,"check_circle"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.wifiConnectMessage())}}function WI(n,t){if(n&1&&(l(0,"div",293)(1,"span",29),u(2,"error"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.wifiConnectError())}}function KI(n,t){if(n&1){let e=L();l(0,"div",232)(1,"div",280)(2,"span",278),u(3,"wifi"),d(),l(4,"span"),u(5,"Connect your phone and computer on the same Wi-Fi network:"),d()(),l(6,"div",281)(7,"div",282)(8,"div",283)(9,"label",284),u(10,"Device IP Address"),d(),l(11,"div",285)(12,"span",286),u(13,"router"),d(),l(14,"input",287),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.wifiHost.set(i))}),d(),Rt(),d()(),l(15,"div",288)(16,"label",284),u(17,"Port"),d(),l(18,"div",285)(19,"span",286),u(20,"tag"),d(),l(21,"input",289),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.wifiPort.set(i))}),d(),Rt(),d()(),l(22,"div",290)(23,"button",291),k("click",function(){v(e);let i=m(3);return y(i.connectWifiDevice())}),M(24,UI,4,0)(25,GI,4,0),d()()(),M(26,qI,5,1,"div",292),M(27,WI,5,1,"div",293),d(),l(28,"div",294)(29,"span",295),u(30,"help_outline"),d(),l(31,"div",296),u(32," Find IP & port in phone "),l(33,"strong"),u(34,"Settings > Developer Options > Wireless Debugging"),d(),u(35,". "),d()()()}if(n&2){let e=m(3);g(14),R("ngModel",e.wifiHost()),Ft(),g(7),R("ngModel",e.wifiPort()),Ft(),g(2),R("disabled",e.isConnectingWifi()||e.isRemoteAdbServer()||!e.wifiHost().trim()),g(),b(e.isConnectingWifi()?24:25),g(2),b(e.wifiConnectMessage()?26:-1),g(),b(e.wifiConnectError()?27:-1)}}function YI(n,t){n&1&&(l(0,"span",147),u(1,"sync"),d(),l(2,"span"),u(3,"Checking..."),d())}function ZI(n,t){n&1&&(l(0,"span",29),u(1,"bolt"),d(),l(2,"span"),u(3,"Test Connection"),d())}function QI(n,t){if(n&1&&(l(0,"div",311)(1,"span",29),u(2,"check_circle"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.remoteAdbMessage())}}function JI(n,t){if(n&1&&(l(0,"div",312)(1,"span",29),u(2,"error"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(4);g(4),O(e.remoteAdbError())}}function XI(n,t){if(n&1&&(l(0,"div",316)(1,"span",29),u(2,"phone_android"),d(),l(3,"div",317)(4,"strong"),u(5),d(),l(6,"span"),u(7),d()(),l(8,"span",318),u(9),d()()),n&2){let e=t.$implicit;g(5),O(e.model||e.serial),g(2),O(e.serial),g(),w("ready",e.state==="device"),g(),V(" ",e.state==="device"?"Ready":e.state," ")}}function eT(n,t){if(n&1&&(l(0,"div",313),G(1,XI,10,5,"div",316,Pm),d()),n&2){let e=m(4);g(),q(e.remoteAdbDevices())}}function tT(n,t){n&1&&(l(0,"span",147),u(1,"sync"),d(),l(2,"span"),u(3,"Connecting..."),d())}function nT(n,t){if(n&1&&(l(0,"span",29),u(1,"check"),d(),l(2,"span"),u(3),d()),n&2){let e=m(5);g(3),O(e.remoteAdbHasReadyDevice()?"Use Connection":"Use Anyway")}}function oT(n,t){if(n&1){let e=L();l(0,"button",319),k("click",function(){v(e);let i=m(4);return y(i.activateRemoteAdbServer())}),M(1,tT,4,0)(2,nT,4,1),d()}if(n&2){let e=m(4);R("disabled",e.isActivatingRemoteAdb()),g(),b(e.isActivatingRemoteAdb()?1:2)}}function iT(n,t){if(n&1){let e=L();l(0,"div",233)(1,"div",297)(2,"span",278),u(3,"hub"),d(),l(4,"span"),u(5,"Enter the host and port of an ADB server endpoint that this computer can access, so Artemis can discover and control a phone physically connected to another computer."),d()(),l(6,"div",298)(7,"div",299)(8,"div",300)(9,"label",301),u(10,"ADB Server Host"),d(),l(11,"div",302)(12,"span",286),u(13,"dns"),d(),l(14,"input",303),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.updateRemoteAdbHost(i))}),d(),Rt(),d()(),l(15,"div",304)(16,"label",305),u(17,"ADB Server Port"),d(),l(18,"div",302)(19,"span",286),u(20,"tag"),d(),l(21,"input",306),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.updateRemoteAdbPort(i))}),d(),Rt(),d()(),l(22,"div",307)(23,"button",308),k("click",function(){v(e);let i=m(3);return y(i.connectRemoteAdbServer())}),M(24,YI,4,0)(25,ZI,4,0),d()()(),l(26,"label",309)(27,"input",310),k("ngModelChange",function(i){v(e);let r=m(3);return y(r.rememberRemoteAdb.set(i))}),d(),Rt(),l(28,"span"),u(29,"Remember this endpoint for future Artemis launches"),d()(),M(30,QI,5,1,"div",311),M(31,JI,5,1,"div",312),M(32,eT,3,0,"div",313),M(33,oT,3,2,"button",314),d(),l(34,"p",315),u(35," Artemis sends all ADB commands to this server instead of the default local server. Any phone visible to that server becomes available to Artemis. "),d()()}if(n&2){let e=m(3);g(14),R("ngModel",e.remoteAdbHost()),Ft(),g(7),R("ngModel",e.remoteAdbPort()),Ft(),g(2),R("disabled",e.isConnectingRemoteAdb()||e.isActivatingRemoteAdb()||!e.remoteAdbHost().trim()),g(),b(e.isConnectingRemoteAdb()?24:25),g(3),R("ngModel",e.rememberRemoteAdb()),Ft(),g(3),b(e.remoteAdbMessage()?30:-1),g(),b(e.remoteAdbError()?31:-1),g(),b(e.remoteAdbDevices().length>0?32:-1),g(),b(e.remoteAdbProbeResult()?.success&&!e.isProbedRemoteAdbActive()?33:-1)}}function rT(n,t){if(n&1){let e=L();l(0,"div",67)(1,"div",221)(2,"button",222),k("click",function(){v(e);let i=m(2);return y(i.setAdbGuideTab("emulator"))}),l(3,"div",223)(4,"div",224)(5,"span",29),u(6,"smartphone"),d()(),l(7,"div",225)(8,"div",226)(9,"span",227),u(10,"Android Emulator"),d()(),l(11,"span",228),M(12,TI,1,2)(13,AI,1,0),d()()(),l(14,"div",229),$(15,"span",230),d()(),l(16,"button",222),k("click",function(){v(e);let i=m(2);return y(i.setAdbGuideTab("usb"))}),l(17,"div",223)(18,"div",224)(19,"span",29),u(20,"usb"),d()(),l(21,"div",225)(22,"div",226)(23,"span",227),u(24,"USB Cable"),d()(),l(25,"span",228),u(26,"Connect physical phone via USB"),d()()(),l(27,"div",229),$(28,"span",230),d()(),l(29,"button",222),k("click",function(){v(e);let i=m(2);return y(i.setAdbGuideTab("wifi"))}),l(30,"div",223)(31,"div",224)(32,"span",29),u(33,"wifi"),d()(),l(34,"div",225)(35,"div",226)(36,"span",227),u(37,"Wireless ADB"),d()(),l(38,"span",228),u(39,"Connect over local Wi-Fi"),d()()(),l(40,"div",229),$(41,"span",230),d()(),l(42,"button",222),k("click",function(){v(e);let i=m(2);return y(i.setAdbGuideTab("remote"))}),l(43,"div",223)(44,"div",224)(45,"span",29),u(46,"devices"),d()(),l(47,"div",225)(48,"div",226)(49,"span",227),u(50,"Remote Computer"),d()(),l(51,"span",228),u(52,"Phone connected to another computer"),d()()(),l(53,"div",229),$(54,"span",230),d()()(),M(55,NI,14,6,"div",231),M(56,BI,3,1,"div",232),M(57,HI,65,0,"div",232),M(58,KI,36,6,"div",232),M(59,iT,36,9,"div",233),d()}if(n&2){let e=m(2);g(2),w("active",e.activeAdbGuideTab()==="emulator"),g(10),b(e.installedAvds().length>0?12:13),g(3),w("checked",e.activeAdbGuideTab()==="emulator"),g(),w("active",e.activeAdbGuideTab()==="usb"),g(12),w("checked",e.activeAdbGuideTab()==="usb"),g(),w("active",e.activeAdbGuideTab()==="wifi"),g(12),w("checked",e.activeAdbGuideTab()==="wifi"),g(),w("active",e.activeAdbGuideTab()==="remote"),g(12),w("checked",e.activeAdbGuideTab()==="remote"),g(),b(e.activeAdbGuideTab()!=="remote"&&e.isRemoteAdbServer()?55:-1),g(),b(e.activeAdbGuideTab()==="emulator"?56:-1),g(),b(e.activeAdbGuideTab()==="usb"?57:-1),g(),b(e.activeAdbGuideTab()==="wifi"?58:-1),g(),b(e.activeAdbGuideTab()==="remote"?59:-1)}}function aT(n,t){n&1&&(l(0,"span",167),u(1,"\u2713 Active"),d())}function sT(n,t){if(n&1){let e=L();l(0,"button",165),k("click",function(){let i=v(e).$implicit,r=m(4);return y(r.selectTargetDevice(i.serial))}),l(1,"span",166),u(2," phone_android "),d(),l(3,"span"),u(4),d(),M(5,aT,2,0,"span",167),d()}if(n&2){let e=t.$implicit,o=m(4);w("selected",e.serial===o.activeDevice()?.serial),g(4),O(e.model||e.serial),g(),b(e.serial===o.activeDevice()?.serial?5:-1)}}function cT(n,t){if(n&1&&(l(0,"div",326)(1,"span",162),u(2,"Select Active Device:"),d(),l(3,"div",163),G(4,sT,6,4,"button",164,Pm),d()()),n&2){let e=m(3);g(4),q(e.connectedDevices())}}function lT(n,t){if(n&1){let e=L();l(0,"div",68)(1,"div",320)(2,"span",321),u(3,"lock"),d(),l(4,"div",322)(5,"div",323),u(6),d(),l(7,"div",324),u(8),d()(),l(9,"button",325),k("click",function(){v(e);let i=m(2);return y(i.refreshReadiness())}),l(10,"span",29),u(11,"refresh"),d(),l(12,"span"),u(13),d()()(),M(14,cT,6,0,"div",326),d()}if(n&2){let e=m(2);g(6),V(" ",e.adbProbe()?.summary==="Device Locked"?"Device Is Locked":"Unable to Verify Lock State"," "),g(2),V(" ",e.adbProbe()?.description," "),g(),R("disabled",e.isRefreshingDiagnostics()),g(),w("spinning",e.isRefreshingDiagnostics()),g(3),O(e.isRefreshingDiagnostics()?"Checking...":"Check Again"),g(),b(e.connectedDevices().length>1?14:-1)}}function dT(n,t){if(n&1){let e=L();l(0,"div",69)(1,"div",327)(2,"span",321),u(3,"screen_lock_portrait"),d(),l(4,"div",328)(5,"div",329),u(6,"USB Debugging Authorization Pending"),d(),l(7,"div",330),u(8," Your Android device was detected, but it requires authorization. Please unlock your phone screen and tap "),l(9,"strong"),u(10,'"Allow USB Debugging"'),d(),u(11,". "),d()(),l(12,"button",331),k("click",function(){v(e);let i=m(2);return y(i.restartAdbServer())}),l(13,"span",29),u(14,"refresh"),d(),l(15,"span"),u(16),d()()()()}if(n&2){let e=m(2);g(16),O(e.isRemoteAdbServer()?"Refresh Devices":"Restart ADB & Check")}}function uT(n,t){n&1&&(l(0,"span",337),u(1,"Detected"),d())}function gT(n,t){n&1&&(l(0,"span",337),u(1,"Detected"),d())}function pT(n,t){n&1&&(l(0,"span",337),u(1,"Detected"),d())}function fT(n,t){if(n&1){let e=L();l(0,"div",70)(1,"div",332)(2,"span",333),u(3,"error"),d(),l(4,"div")(5,"strong"),u(6,"Android Platform Tools (adb) not found."),d(),u(7," Please install ADB on your operating system: "),d()(),l(8,"div",334)(9,"div",335)(10,"div",336)(11,"span"),u(12,"Linux (Ubuntu / Debian)"),d(),M(13,uT,2,0,"span",337),d(),l(14,"div",81)(15,"span",84),u(16,"$"),d(),l(17,"code",85),u(18,"sudo apt-get install -y adb"),d(),l(19,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard("sudo apt-get install -y adb","linux_adb"))}),l(20,"span",29),u(21),d(),l(22,"span"),u(23),d()()()(),l(24,"div",335)(25,"div",336)(26,"span"),u(27,"macOS (Homebrew)"),d(),M(28,gT,2,0,"span",337),d(),l(29,"div",81)(30,"span",84),u(31,"$"),d(),l(32,"code",85),u(33,"brew install android-platform-tools"),d(),l(34,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard("brew install android-platform-tools","mac_adb"))}),l(35,"span",29),u(36),d(),l(37,"span"),u(38),d()()()(),l(39,"div",335)(40,"div",336)(41,"span"),u(42,"Windows (WinGet)"),d(),M(43,pT,2,0,"span",337),d(),l(44,"div",81)(45,"span",84),u(46,">"),d(),l(47,"code",85),u(48,"winget install Google.PlatformTools"),d(),l(49,"button",82),k("click",function(){v(e);let i=m(2);return y(i.copyToClipboard("winget install Google.PlatformTools","win_adb"))}),l(50,"span",29),u(51),d(),l(52,"span"),u(53),d()()()()()()}if(n&2){let e=m(2);g(9),w("highlight-os",e.effectiveOs()==="linux"),g(4),b(e.effectiveOs()==="linux"?13:-1),g(6),w("copied",e.copiedId()==="linux_adb"),g(2),O(e.copiedId()==="linux_adb"?"check":"content_copy"),g(2),O(e.copiedId()==="linux_adb"?"Copied!":"Copy"),g(),w("highlight-os",e.effectiveOs()==="darwin"),g(4),b(e.effectiveOs()==="darwin"?28:-1),g(6),w("copied",e.copiedId()==="mac_adb"),g(2),O(e.copiedId()==="mac_adb"?"check":"content_copy"),g(2),O(e.copiedId()==="mac_adb"?"Copied!":"Copy"),g(),w("highlight-os",e.effectiveOs()==="windows"),g(4),b(e.effectiveOs()==="windows"?43:-1),g(6),w("copied",e.copiedId()==="win_adb"),g(2),O(e.copiedId()==="win_adb"?"check":"content_copy"),g(2),O(e.copiedId()==="win_adb"?"Copied!":"Copy")}}function mT(n,t){if(n&1){let e=L();l(0,"div",71)(1,"div",338)(2,"div",339)(3,"span",23),u(4,"check"),d()(),l(5,"div",340)(6,"h4",341),u(7,"System Ready for Autonomous Tasks"),d(),l(8,"p",342),u(9,"All dependencies, AI model credentials, and Android device are verified and ready to run."),d()()(),l(10,"button",343),k("click",function(){v(e);let i=m(2);return y(i.setTab("launcher"))}),l(11,"span"),u(12,"Launch Autonomous Task"),d(),l(13,"span",344),u(14,"rocket_launch"),d()()()}}function _T(n,t){if(n&1){let e=L();l(0,"section",18)(1,"div",20)(2,"div",21)(3,"div",22),M(4,vD,2,0,"span",23)(5,yD,2,0,"span"),d(),l(6,"div",24)(7,"div",25)(8,"h3",26),u(9,"System & Environment"),d()(),l(10,"p",27),u(11,"Core tools, Android debug bridge, and video recording runtime"),d()(),l(12,"button",28),k("click",function(){v(e);let i=m();return y(i.refreshReadiness())}),l(13,"span",29),u(14,"refresh"),d(),l(15,"span"),u(16),d()()(),l(17,"div",30),M(18,xD,26,11,"div",31),l(19,"div",32)(20,"div",33)(21,"div",34)(22,"div",35)(23,"span",36),u(24,"terminal"),d()(),l(25,"div",37)(26,"div",38),u(27,"Python Runtime"),d(),l(28,"div",39),u(29),d()(),l(30,"div",40),$(31,"span",41),d()(),M(32,wD,12,4,"div",42),d(),l(33,"div",33)(34,"div",34)(35,"div",35)(36,"span",36),u(37,"construction"),d()(),l(38,"div",37)(39,"div",38),u(40,"Android Platform-Tools"),d(),l(41,"div",39),u(42),d()(),l(43,"div",40),$(44,"span",41),d()(),M(45,SD,12,5,"div",42),d(),l(46,"div",33)(47,"div",34)(48,"div",35)(49,"span",36),u(50,"tune"),d()(),l(51,"div",37)(52,"div",38),u(53,"System Configuration"),d(),l(54,"div",39),u(55),d()(),l(56,"div",40),$(57,"span",41),d()(),M(58,kD,12,4,"div",42),d(),l(59,"div",33)(60,"div",34)(61,"div",35)(62,"span",36),u(63,"videocam"),d()(),l(64,"div",37)(65,"div",38),u(66,"Video Toolchain"),d(),l(67,"div",39),u(68),d()(),l(69,"div",40),$(70,"span",41),d()(),M(71,ED,12,5,"div",42),d()()()(),l(72,"div",20)(73,"div",21)(74,"div",22),M(75,DD,2,0,"span",23)(76,ID,2,0,"span"),d(),l(77,"div",24)(78,"div",25)(79,"h3",26),u(80,"AI Model Setup"),d()(),l(81,"p",27),u(82,"Google Gemini or custom LLM providers (OpenAI, Claude, Ollama)"),d()()(),l(83,"div",30)(84,"div",43)(85,"div",44),k("click",function(){v(e);let i=m();return y(i.setModelSetupMode("gemini"))}),l(86,"div",45)(87,"div",46),$(88,"span",47),d(),l(89,"div",48),yo(),l(90,"svg",49)(91,"defs")(92,"linearGradient",50),$(93,"stop",51)(94,"stop",52)(95,"stop",53),d()(),$(96,"path",54),d()(),rr(),l(97,"div",55)(98,"div",56)(99,"span",57),u(100,"Google Gemini"),d(),l(101,"span",58),u(102,"Recommended"),d()(),l(103,"p",59),u(104,"Fast multimodal reasoning with free API key from Google AI Studio."),d()()()(),l(105,"div",44),k("click",function(){v(e);let i=m();return y(i.setModelSetupMode("custom"))}),l(106,"div",45)(107,"div",46),$(108,"span",47),d(),l(109,"div",60)(110,"span",29),u(111,"tune"),d()(),l(112,"div",55)(113,"div",56)(114,"span",57),u(115,"Custom Configuration"),d()(),l(116,"p",59),u(117,"Define models in "),l(118,"code"),u(119,"config/artemis.jsonc"),d(),u(120," & keys in "),l(121,"code"),u(122,".env"),d(),u(123," (OpenAI, Claude, Ollama)."),d()()()()(),M(124,KD,42,18)(125,tI,58,15,"div",61),d()(),l(126,"div",20)(127,"div",21)(128,"div",22),M(129,nI,2,0,"span",23)(130,oI,2,0,"span"),d(),l(131,"div",24)(132,"div",25)(133,"h3",26),u(134,"Android Device & Emulator Connection"),d()(),l(135,"p",27),u(136,"Local emulator, attached phone, wireless device, or another ADB server"),d()(),l(137,"button",62),k("click",function(){v(e);let i=m();return y(i.toggleConnectionMethods())}),l(138,"span",29),u(139,"swap_horiz"),d(),l(140,"span"),u(141),d()(),l(142,"button",63),k("click",function(){v(e);let i=m();return y(i.restartAdbServer())}),M(143,iI,4,0)(144,rI,4,1)(145,aI,4,1),d()(),l(146,"div",30),M(147,fI,20,7,"div",64),M(148,SI,74,34,"div",65),M(149,II,26,3,"div",66),M(150,rT,60,22,"div",67),M(151,lT,15,7,"div",68),M(152,dT,17,1,"div",69),M(153,fT,54,21,"div",70),d()(),M(154,mT,15,0,"div",71),d()}if(n&2){let e=m();g(),w("card-pass",e.isEnvironmentReady())("card-warn",!e.isEnvironmentReady()),g(2),w("step-pass",e.isEnvironmentReady()),g(),b(e.isEnvironmentReady()?4:5),g(8),R("disabled",e.isRefreshingDiagnostics()),g(),w("spinning",e.isRefreshingDiagnostics()),g(3),O(e.isRefreshingDiagnostics()?"Checking...":"Re-check"),g(2),b(e.hasReadinessReport()&&!e.isEnvironmentReady()?18:-1),g(2),w("item-pass",e.pythonProbe()?.status==="pass")("item-fail",e.pythonProbe()?.status==="fail"),g(9),O(e.pythonProbe()?.summary||"Checking\u2026"),g(2),w("dot-pass",e.pythonProbe()?.status==="pass")("dot-fail",e.pythonProbe()?.status==="fail"),g(),b(e.pythonProbe()?.status==="fail"?32:-1),g(),w("item-pass",e.adbProbe()?.metadata?.installed)("item-fail",e.adbProbe()?.status==="fail"),g(9),O(e.adbProbe()?e.adbProbe()?.metadata?.installed?"ADB Installed":"ADB Missing":"Checking\u2026"),g(2),w("dot-pass",e.adbProbe()?.metadata?.installed)("dot-fail",e.adbProbe()?.status==="fail"),g(),b(e.adbProbe()?.status==="fail"?45:-1),g(),w("item-pass",e.configProbe()?.status==="pass")("item-fail",e.configProbe()?.status==="fail"),g(9),O(e.configProbe()?.summary||"Config Valid"),g(2),w("dot-pass",e.configProbe()?.status==="pass")("dot-fail",e.configProbe()?.status==="fail"),g(),b(e.configProbe()?.status==="fail"?58:-1),g(),w("item-pass",e.toolchainProbe()?.status==="pass")("item-fail",e.toolchainProbe()?.status!=="pass"),g(9),O(e.toolchainProbe()?.summary||"FFmpeg & scrcpy"),g(2),w("dot-pass",e.toolchainProbe()?.status==="pass")("dot-fail",e.toolchainProbe()?.status!=="pass"),g(),b(e.toolchainProbe()?.status!=="pass"?71:-1),g(),w("card-pass",e.isCredentialsReady())("card-warn",!e.isCredentialsReady()),g(2),w("step-pass",e.isCredentialsReady()),g(),b(e.isCredentialsReady()?75:76),g(10),w("selected",e.modelSetupMode()==="gemini"),g(3),w("checked",e.modelSetupMode()==="gemini"),g(17),w("selected",e.modelSetupMode()==="custom"),g(3),w("checked",e.modelSetupMode()==="custom"),g(16),b(e.modelSetupMode()==="gemini"?124:125),g(2),w("card-pass",e.isDeviceReady())("card-warn",!e.isDeviceReady()),g(2),w("step-pass",e.isDeviceReady()),g(),b(e.isDeviceReady()?129:130),g(12),O(e.showConnectionMethods()?"Hide Options":"Change Connection"),g(),R("disabled",e.isRestartingAdb())("title",e.isRemoteAdbServer()?"Refresh devices without stopping the remote ADB server":"Restart local ADB daemon"),g(),b(e.isRestartingAdb()?143:e.adbRestartFeedback()?144:145),g(4),b(e.isDeviceReady()?147:-1),g(),b(e.isEmulatorLaunching()||!e.isDeviceReady()&&e.adbProbe()?.summary==="Device Booting"?148:-1),g(),b(!e.isDeviceReady()&&!e.isEmulatorLaunching()&&e.emulatorLaunchState()?.status==="failed"?149:-1),g(),b(e.showConnectionMethods()||!e.isDeviceReady()&&!e.isEmulatorLaunching()&&e.adbProbe()?.summary!=="Device Booting"&&e.adbProbe()?.summary!=="Device Unauthorized"&&e.adbProbe()?.summary!=="Device Locked"&&e.adbProbe()?.summary!=="Lock State Unknown"&&e.adbProbe()?.status!=="fail"&&e.emulatorLaunchState()?.status!=="failed"?150:-1),g(),b(e.adbProbe()?.status==="warn"&&(e.adbProbe()?.summary==="Device Locked"||e.adbProbe()?.summary==="Lock State Unknown")?151:-1),g(),b(e.adbProbe()?.status==="warn"&&e.adbProbe()?.summary==="Device Unauthorized"?152:-1),g(),b(e.adbProbe()?.status==="fail"?153:-1),g(),b(e.isReady()?154:-1)}}function hT(n,t){if(n&1){let e=L();l(0,"div",381),k("click",function(){v(e);let i=m(2);return y(i.setTab("diagnostics"))}),l(1,"span",382),u(2),d(),l(3,"span"),u(4),d(),l(5,"span",383),u(6,"arrow_forward"),d()()}if(n&2){let e=m(2);g(2),O(e.adbProbe()?.summary==="Device Locked"?"lock":"warning"),g(2),V(" ",e.adbProbe()?.summary==="Device Locked"?"Android device is locked. Unlock it and enter the home screen before running a task.":"Prerequisites incomplete: runtime, credentials, or Android device is not ready. Click here to open Setup Guide."," ")}}function CT(n,t){if(n&1){let e=L();l(0,"div",347)(1,"div",384)(2,"span",385),u(3,"auto_awesome"),d(),l(4,"span",386),u(5," Continuous monitoring or complex task detected. Recommended: "),l(6,"strong"),u(7,"Pro Mode"),d()()(),l(8,"button",387),k("click",function(){v(e);let i=m(2);return y(i.setProfile("pro"))}),l(9,"span"),u(10,"Switch to Pro"),d(),l(11,"span",388),u(12,"arrow_forward"),d()()()}}function MT(n,t){n&1&&(l(0,"p",353)(1,"span",389),u(2,"Flash"),d(),l(3,"span",390),u(4,"\xB7"),d(),l(5,"span"),u(6,"Token-efficient, rapid "),l(7,"strong"),u(8,"3\u20135s/step"),d(),u(9," decisions straight from the live screen. Best for everyday tasks with a clear path, however long they run."),d(),l(10,"span",391),u(11,"(No plan, no result check, no ADB commands)"),d()())}function bT(n,t){n&1&&(l(0,"p",353)(1,"span",389),u(2,"Pro"),d(),l(3,"span",390),u(4,"\xB7"),d(),l(5,"span"),u(6,"Our most powerful mode. Plans first, checks its own results and recovers when a tap is blocked. Handles "),l(7,"strong"),u(8,"100+ step"),d(),u(9," workflows and live phone monitoring ("),l(10,"strong"),u(11,"15\u201340s"),d(),u(12," / step)."),d()())}function PT(n,t){if(n&1&&(l(0,"div",354)(1,"span",392),u(2,"error"),d(),l(3,"span"),u(4),d()()),n&2){let e=m(2);g(4),O(e.errorMessage())}}function OT(n,t){if(n&1&&(l(0,"span",363),u(1),d()),n&2){let e=m(2);g(),V("",e.taskGoal().length," chars")}}function vT(n,t){n&1&&($(0,"span",393),l(1,"span"),u(2,"Starting..."),d())}function yT(n,t){n&1&&(l(0,"span",394),u(1,"play_arrow"),d(),l(2,"span"),u(3,"Run Task"),d())}function xT(n,t){if(n&1){let e=L();l(0,"button",408),k("click",function(){v(e);let i=m(3);return y(i.expectedOutput.set(""))}),l(1,"span",29),u(2,"close"),d()()}}function wT(n,t){if(n&1){let e=L();l(0,"button",409),k("click",function(){v(e);let i=m(3);return y(i.resetTuning())}),u(1,"Reset"),d()}}function ST(n,t){if(n&1&&$(0,"span",422),n&2){let e=t.$index,o=m().$implicit,i=m(3);Se("--%NS%n",i.notchFraction(e,o.ladder.length)),w("reached",e<=o.index)}}function kT(n,t){if(n&1&&(yo(),$(0,"rect",426)),n&2){let e=t.$implicit;Se("opacity",e.opacity)("animation-delay",e.delay,"ms"),Wt("x",e.x+"%")("y",e.y)}}function ET(n,t){if(n&1&&($(0,"div",423),yo(),l(1,"svg",424),G(2,kT,1,6,":svg:rect",425,Do),d()),n&2){let e=m(4);g(2),q(e.ditherPixels)}}function DT(n,t){n&1&&$(0,"span",420)}function IT(n,t){if(n&1&&(l(0,"li"),u(1),d()),n&2){let e=t.$implicit;g(),O(e)}}function TT(n,t){if(n&1&&(l(0,"li"),u(1),d()),n&2){let e=t.$implicit;g(),O(e)}}function AT(n,t){if(n&1&&(l(0,"div",435)(1,"span",434),u(2,"Not included"),d(),l(3,"ul"),G(4,TT,2,1,"li",null,Do),d()()),n&2){let e=m(2).$implicit;g(4),q(e.level.skips)}}function NT(n,t){if(n&1&&($(0,"div",427),l(1,"div",428)(2,"div",429)(3,"span",430),u(4),d(),l(5,"span",431),u(6),d()(),l(7,"p",432),u(8),d(),l(9,"div",433)(10,"span",434),u(11,"What happens"),d(),l(12,"ul"),G(13,IT,2,1,"li",null,Do),d()(),M(15,AT,6,0,"div",435),l(16,"div",436)(17,"span",434),u(18,"Use it for"),d(),l(19,"span"),u(20),d()()()),n&2){let e=m().$implicit;g(4),O(e.level.label),g(2),O(e.level.latency),g(2),O(e.level.tagline),g(5),q(e.level.runs),g(2),b(e.level.skips?.length?15:-1),g(5),O(e.level.bestFor)}}function RT(n,t){if(n&1){let e=L();l(0,"div",410)(1,"div",411)(2,"span",412),u(3),d(),l(4,"span",413),u(5),d()(),l(6,"div",414)(7,"span"),u(8),d(),l(9,"span"),u(10),d()(),l(11,"div",415),k("pointerenter",function(){let i=v(e).$implicit,r=m(3);return y(r.showTuningTip(i.kind))})("pointerleave",function(){let i=v(e).$implicit,r=m(3);return y(r.hideTuningTip(i.kind))}),l(12,"div",416),$(13,"div",417),G(14,ST,1,4,"span",418,qP),M(16,ET,4,0),d(),$(17,"div",419),M(18,DT,1,0,"span",420),l(19,"input",421),k("input",function(i){let r=v(e).$implicit,a=m(3);return y(a.setTuningIndex(r.kind,i.target.value))})("focus",function(){let i=v(e).$implicit,r=m(3);return y(r.showTuningTip(i.kind))})("blur",function(){let i=v(e).$implicit,r=m(3);return y(r.hideTuningTip(i.kind))}),d(),M(20,NT,21,5),d()()}if(n&2){let e=t.$implicit,o=m(3);Se("--%NS%pct",e.fraction),w("verify",e.kind==="verify")("explore",e.kind==="explore")("dragging",o.activeTuningTip()===e.kind),g(3),O(e.name),g(2),O(e.level.label),g(3),O(e.ends[0]),g(2),O(e.ends[1]),g(4),q(e.ladder),g(2),b(e.index===e.ladder.length-1?16:-1),g(2),b(e.index===e.ladder.length-1?18:-1),g(),R("max",e.ladder.length-1)("value",e.index),Wt("aria-label",e.name)("aria-valuetext",e.level.label+": "+e.level.tagline),g(),b(o.activeTuningTip()===e.kind?20:-1)}}function FT(n,t){if(n&1){let e=L();l(0,"div",395)(1,"div",396)(2,"span",397),u(3,"Expected Output"),d(),l(4,"span",398),u(5,"(Optional)"),d()(),l(6,"div",399)(7,"input",400),k("ngModelChange",function(i){v(e);let r=m(2);return y(r.expectedOutput.set(i))}),d(),Rt(),M(8,xT,3,0,"button",401),d()(),l(9,"div",402)(10,"div",403)(11,"span",404),u(12,"Pro settings"),d(),M(13,wT,2,0,"button",405),d(),l(14,"div",406),G(15,RT,21,19,"div",407,CD),d()()}if(n&2){let e=m(2);g(7),R("ngModel",e.expectedOutput()),Ft(),g(),b(e.expectedOutput().trim()?8:-1),g(),w("tip-open",e.activeTuningTip()!==null),g(4),b(e.isTuningDefault()?-1:13),g(2),q(e.tuningSliders())}}function LT(n,t){if(n&1&&(l(0,"div",440)(1,"span",29),u(2),d()()),n&2){let e=t.$implicit;R("title",e.name),g(2),O(e.icon)}}function VT(n,t){if(n&1){let e=L();l(0,"div",437),k("click",function(){let i=v(e).$implicit,r=m(2);return y(r.applySuggestion(i))}),l(1,"div",438)(2,"div",439),G(3,LT,3,2,"div",440,GP),l(5,"span",441),u(6),d()(),l(7,"span",442),u(8),d()(),l(9,"div",443)(10,"h4",444),u(11),d(),l(12,"p",445),u(13),d()(),l(14,"div",446)(15,"span",447),u(16),d(),l(17,"div",448)(18,"span"),u(19,"Use"),d(),l(20,"span",449),u(21,"arrow_forward"),d()()()()}if(n&2){let e=t.$implicit,o=m(2);w("is-pro-card",e.profile==="pro")("is-flash-card",e.profile==="flash"),g(3),q(e.apps),g(3),O(o.getAppNamesDisplay(e.apps)),g(),w("mode-pro",e.profile==="pro")("mode-flash",e.profile==="flash"),R("title",e.profile==="pro"?"Uses ARTEMIS Pro (Multi-Agent Cognitive State Graph)":"Uses ARTEMIS Flash (Reactive Fast Loop)"),g(),V(" ",e.profile==="pro"?"Pro":"Flash"," "),g(3),O(e.title),g(2),O(e.description),g(3),O(e.tag)}}function jT(n,t){if(n&1){let e=L();l(0,"section",19),M(1,hT,7,2,"div",345),l(2,"div",346),M(3,CT,13,0,"div",347),l(4,"div",348)(5,"textarea",349),hr("ngModelChange",function(i){v(e);let r=m();return $a(r.taskGoal,i)||(r.taskGoal=i),y(i)}),k("keydown.control.enter",function(){v(e);let i=m();return y(i.runTask())})("keydown.meta.enter",function(){v(e);let i=m();return y(i.runTask())}),d(),Rt(),d(),l(6,"div",350)(7,"div",351)(8,"span",29),u(9),d()(),l(10,"div",352),M(11,MT,12,0,"p",353)(12,bT,13,0,"p",353),d()(),M(13,PT,5,1,"div",354),l(14,"div",355)(15,"div",356)(16,"div",357)(17,"button",358),k("click",function(){v(e);let i=m();return y(i.setProfile("flash"))}),l(18,"span",359),u(19,"bolt"),d(),l(20,"span",360),u(21,"Flash"),d()(),l(22,"button",361),k("click",function(){v(e);let i=m();return y(i.setProfile("pro"))}),l(23,"span",359),u(24,"psychology"),d(),l(25,"span",360),u(26,"Pro"),d()()()(),l(27,"div",362),M(28,OT,2,1,"span",363),l(29,"button",364),k("click",function(){v(e);let i=m();return y(i.runTask())}),M(30,vT,3,0)(31,yT,4,0),d()()()(),M(32,FT,17,5),l(33,"div",365)(34,"div",366)(35,"div",367)(36,"div",368)(37,"span",369),u(38,"auto_awesome"),d(),l(39,"span",370),u(40,"Recommended Tasks"),d()(),l(41,"div",371)(42,"button",372),k("click",function(){v(e);let i=m();return y(i.setCategory("all"))}),l(43,"span"),u(44,"All"),d()(),l(45,"button",373),k("click",function(){v(e);let i=m();return y(i.setCategory("flash"))}),l(46,"span",374),u(47,"bolt"),d(),l(48,"span"),u(49,"Flash"),d()(),l(50,"button",375),k("click",function(){v(e);let i=m();return y(i.setCategory("pro"))}),l(51,"span",374),u(52,"psychology"),d(),l(53,"span"),u(54,"Pro"),d()(),l(55,"button",372),k("click",function(){v(e);let i=m();return y(i.setCategory("cross_app"))}),l(56,"span",374),u(57,"sync_alt"),d(),l(58,"span"),u(59,"Multi-App"),d()(),l(60,"button",372),k("click",function(){v(e);let i=m();return y(i.setCategory("monitor"))}),l(61,"span",374),u(62,"monitoring"),d(),l(63,"span"),u(64,"Monitor"),d()()()(),l(65,"div",376)(66,"button",377),k("click",function(){v(e);let i=m();return y(i.shuffleSuggestions())}),l(67,"span",378),u(68,"shuffle"),d(),l(69,"span"),u(70,"Shuffle"),d()()()(),l(71,"div",379),G(72,VT,22,14,"div",380,qP),d()()()}if(n&2){let e=m();g(),b(e.hasReadinessReport()&&!e.isReady()?1:-1),g(),w("is-pro-active",e.selectedProfile()==="pro"),g(),b(e.showIntentSuggestion()?3:-1),g(2),_r("ngModel",e.taskGoal),R("disabled",e.isSubmitting()),Ft(),g(),w("is-pro",e.selectedProfile()==="pro"),g(3),O(e.selectedProfile()==="flash"?"bolt":"psychology"),g(2),b(e.selectedProfile()==="flash"?11:12),g(2),b(e.errorMessage()?13:-1),g(4),w("active",e.selectedProfile()==="flash"),g(5),w("active",e.selectedProfile()==="pro"),g(6),b(e.taskGoal().length>0?28:-1),g(),w("btn-run-pro",e.selectedProfile()==="pro"),R("disabled",!e.taskGoal().trim()||e.isSubmitting()||!e.isReady())("title",e.isReady()?"Run task":e.adbProbe()?.summary==="Device Locked"?"Unlock the Android device before running":"Complete System Setup before running"),g(),b(e.isSubmitting()?30:31),g(2),b(e.selectedProfile()==="pro"?32:-1),g(10),w("active",e.selectedCategory()==="all"),g(3),w("active",e.selectedCategory()==="flash"),g(5),w("active",e.selectedCategory()==="pro"),g(5),w("active",e.selectedCategory()==="cross_app"),g(5),w("active",e.selectedCategory()==="monitor"),g(12),q(e.filteredSuggestions())}}function $T(n=260,t=7){let e=t>>>0,o=()=>(e=Math.imul(e,1664525)+1013904223>>>0,e/4294967296),i=[];for(let r=0;rthis.systemService.modelConfigEnv());geminiKeyInput=x("");showGeminiKey=x(!1);isSavingGeminiKey=x(!1);isTestingGeminiKey=x(!1);geminiSaveMessage=x(null);geminiSaveError=x(null);isGeminiKeyEdited=x(!1);ocrKeyInput=x("");showOcrKey=x(!1);isSavingOcrKey=x(!1);isTestingOcrKey=x(!1);ocrSaveMessage=x(null);ocrSaveError=x(null);isOcrKeyEdited=x(!1);copiedId=x(null);isRefreshingDiagnostics=x(!1);wifiHost=x("192.168.1.100");wifiPort=x("5555");isConnectingWifi=x(!1);wifiConnectMessage=x(null);wifiConnectError=x(null);adbRestartFeedback=x(null);showConnectionMethods=x(!1);remoteAdbHost=x("127.0.0.1");remoteAdbPort=x("5038");rememberRemoteAdb=x(!0);isConnectingRemoteAdb=x(!1);isActivatingRemoteAdb=x(!1);isSwitchingToLocalAdb=x(!1);remoteAdbMessage=x(null);remoteAdbError=x(null);remoteAdbDevices=x([]);remoteAdbProbeResult=x(null);adbServerStatus=I(()=>this.systemService.adbServerStatus());isRemoteAdbServer=I(()=>this.systemService.isRemoteAdbServer());remoteAdbHasReadyDevice=I(()=>this.remoteAdbDevices().some(t=>t.state==="device"));isProbedRemoteAdbActive=I(()=>{let t=this.remoteAdbProbeResult()?.endpoint,e=this.adbServerStatus()?.endpoint;return!!t&&!!e&&t.identity===e.identity});wifiCommand=I(()=>{let t=this.wifiHost().trim()||"",e=this.wifiPort().trim()||"5555";return`adb connect ${t}:${e}`});selectedProfile=x("flash");taskGoal=x("");isSubmitting=x(!1);errorMessage=x(null);expectedOutput=x("");enableOutputter=x(!0);showOutputterDrawer=x(!1);verificationLevels=_n;explorerModes=hn;verificationIndex=x(Li(_n,Hr,Hr));explorerIndex=x(Li(hn,Ur,Ur));tuningDefaults=x({verification:Hr,explorer:Ur});tuningTouched=x(!1);activeTuningTip=x(null);ditherPixels=$T();verificationLevel=I(()=>_n[this.verificationIndex()]);explorerMode=I(()=>hn[this.explorerIndex()]);isTuningDefault=I(()=>{let t=this.tuningDefaults();return this.verificationLevel().id===t.verification&&this.explorerMode().id===t.explorer});tuningSliders=I(()=>{let t=this.verificationIndex(),e=this.explorerIndex();return[{kind:"verify",name:"Result check",ends:["Off","Strict"],ladder:_n,index:t,level:_n[t],fraction:Gd(t,_n.length)/100},{kind:"explore",name:"Screen reading",ends:["Faster","Sharper"],ladder:hn,index:e,level:hn[e],fraction:Gd(e,hn.length)/100}]});notchFraction(t,e){return Gd(t,e)/100}setTuningIndex(t,e){let o=Math.round(Number(e));if(!Number.isFinite(o))return;let i=t==="verify"?_n:hn,r=Math.min(Math.max(o,0),i.length-1);this.tuningTouched.set(!0),t==="verify"?this.verificationIndex.set(r):this.explorerIndex.set(r),this.activeTuningTip.set(t)}showTuningTip(t){this.activeTuningTip.set(t)}hideTuningTip(t){this.activeTuningTip()===t&&this.activeTuningTip.set(null)}resetTuning(){let t=this.tuningDefaults();this.verificationIndex.set(Li(_n,t.verification,Hr)),this.explorerIndex.set(Li(hn,t.explorer,Ur)),this.tuningTouched.set(!1)}loadProTuningDefaults(){this.agentService.getProTuningDefaults().subscribe({next:t=>{let e=Li(_n,t?.verification_level,Hr),o=Li(hn,t?.explorer_mode,Ur);this.tuningDefaults.set({verification:_n[e].id,explorer:hn[o].id}),this.tuningTouched()||(this.verificationIndex.set(e),this.explorerIndex.set(o))},error:()=>{}})}toggleOutputterDrawer(){this.showOutputterDrawer.update(t=>!t)}applyOutputPreset(t){this.expectedOutput()===t?this.expectedOutput.set(""):(this.expectedOutput.set(t),this.showOutputterDrawer.set(!0))}isIntentSuggestingPro=I(()=>{let t=this.taskGoal().toLowerCase();return t.trim()?["monitor","polling","poll","wait until","loop","keep watching","crash","logcat","troubleshoot","diagnose","debug","investigate","compare","extract","summarize","report","\u76D1\u63A7","\u8F6E\u8BE2","\u7B49\u5F85","\u4E00\u76F4","\u76F4\u5230","\u5D29\u6E83","\u95EA\u9000","\u6392\u67E5","\u5206\u6790\u65E5\u5FD7","\u5BF9\u6BD4","\u603B\u7ED3"].some(o=>t.includes(o)):!1});showIntentSuggestion=I(()=>this.isIntentSuggestingPro()&&this.selectedProfile()==="flash");isReady=I(()=>this.systemService.isReady());hasReadinessReport=I(()=>this.systemService.hasReadinessReport());isLoading=I(()=>this.systemService.isLoading());isRestartingAdb=I(()=>this.systemService.isRestartingAdb());launchingAvd=I(()=>this.systemService.launchingAvd());emulatorLaunchState=I(()=>this.systemService.emulatorLaunchState());isEmulatorLaunching=I(()=>this.systemService.isEmulatorLaunching());showLaunchLogs=x(!1);pythonProbe=I(()=>this.systemService.pythonProbe());configProbe=I(()=>this.systemService.configProbe());adbProbe=I(()=>this.systemService.adbProbe());llmProbe=I(()=>this.systemService.llmProbe());geminiProbe=I(()=>this.systemService.geminiProbe());ocrProbe=I(()=>this.systemService.ocrProbe());toolchainProbe=I(()=>this.systemService.toolchainProbe());isEnvironmentReady=I(()=>this.systemService.isEnvironmentReady());isCredentialsReady=I(()=>this.systemService.isCredentialsReady());isSkipCredentialsCheck=I(()=>this.systemService.isSkipCredentialsCheck());isDeviceReady=I(()=>this.systemService.isDeviceReady());activeDevice=I(()=>this.systemService.activeDevice());connectedDevices=I(()=>this.systemService.connectedDevices());installedAvds=I(()=>this.systemService.installedAvds());emulatorPath=I(()=>this.systemService.emulatorPath());isEmulatorInPath=I(()=>this.systemService.isEmulatorInPath());totalStepCount=I(()=>this.systemService.totalStepCount());passedStepCount=I(()=>this.systemService.passedStepCount());blockerCount=I(()=>this.systemService.blockerCount());passedBlockerCount=I(()=>this.systemService.passedBlockerCount());configuredLlmProviders=I(()=>{let t=this.llmProbe()?.metadata;return t&&Array.isArray(t.providers)?t.providers:[]});selectedOs=x(null);effectiveOs=I(()=>this.selectedOs()||this.systemService.osType());oneClickSetupCmd=I(()=>this.effectiveOs()==="windows"?"powershell -ExecutionPolicy Bypass -File scripts/install_deps.ps1":"bash scripts/install_deps.sh");adbInstallCmd=I(()=>{let t=this.effectiveOs();return t==="windows"?"winget install Google.PlatformTools":t==="darwin"?"brew install android-platform-tools":"sudo apt-get install -y adb"});toolchainInstallCmd=I(()=>{let t=this.effectiveOs();return t==="windows"?"winget install Gyan.FFmpeg Genymobile.scrcpy":t==="darwin"?"brew install ffmpeg scrcpy":"sudo apt-get install -y ffmpeg scrcpy"});emuHypervisorTitle=I(()=>{let t=this.effectiveOs();return t==="windows"?"Enable Windows Hypervisor Platform (WHPX)":t==="darwin"?"Verify macOS Hypervisor / Install Tools":"Enable KVM Hardware Acceleration (Linux)"});emuHypervisorDesc=I(()=>{let t=this.effectiveOs();return t==="windows"?"Enable Windows Hypervisor Platform in PowerShell (Run as Administrator):":t==="darwin"?"macOS uses native Hypervisor.framework. Install SDK tools via Homebrew (or Studio):":"Ensure virtualization permissions are granted to your user account:"});emuHypervisorCmd=I(()=>{let t=this.effectiveOs();return t==="windows"?"Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatform":t==="darwin"?"brew install --cask android-commandlinetools":"sudo apt-get install -y qemu-kvm libvirt-daemon-system && sudo adduser $USER kvm"});emuSdkInstallCmd=I(()=>this.effectiveOs()==="darwin"?'sdkmanager --install "system-images;android-34;google_apis;arm64-v8a" "emulator" "platform-tools"':'sdkmanager --install "system-images;android-34;google_apis;x86_64" "emulator" "platform-tools"');emuCreateAvdCmd=I(()=>this.effectiveOs()==="darwin"?'avdmanager create avd -n Pixel_8_API_34 -k "system-images;android-34;google_apis;arm64-v8a" --device "pixel_8"':'avdmanager create avd -n Pixel_8_API_34 -k "system-images;android-34;google_apis;x86_64" --device "pixel_8"');isOcrConfigured=I(()=>this.ocrProbe()?.metadata?.configured===!0);currentApiKey=I(()=>this.systemService.currentApiKey());apiKeysMap=I(()=>this.systemService.apiKeysMap());savedGeminiKey=I(()=>this.apiKeysMap().google||this.currentApiKey()||"");isGeminiModified=I(()=>this.geminiKeyInput().trim()!==this.savedGeminiKey().trim());savedOcrKey=I(()=>this.apiKeysMap().ocr||"");isOcrModified=I(()=>this.ocrKeyInput().trim()!==this.savedOcrKey().trim());allSuggestions=this.taskRecService.allTasks;selectedCategory=x("all");shuffleOffset=x(0);installedPackages=I(()=>{let t=this.activeDevice()?.installed_packages;return t&&Array.isArray(t)?new Set(t):new Set});filteredSuggestions=I(()=>this.taskRecService.filterAndRankTasks(this.installedPackages(),this.selectedCategory(),this.shuffleOffset()));focusListener=()=>{this.systemService.fetchReadiness().subscribe()};constructor(){Ye(()=>{let t=this.systemService.apiKeysMap(),e=this.systemService.currentApiKey(),o=t.google||e||"",i=t.ocr||"";this.isGeminiKeyEdited()||this.geminiKeyInput.set(o),this.isOcrKeyEdited()||this.ocrKeyInput.set(i)})}ngOnInit(){this.loadProTuningDefaults(),this.systemService.fetchReadiness().subscribe(),this.systemService.fetchModelConfigEnv().subscribe(),this.systemService.fetchAdbServerStatus().subscribe({next:t=>{t.endpoint.mode==="remote"&&(this.remoteAdbHost.set(t.endpoint.host),this.remoteAdbPort.set(String(t.endpoint.port)),this.activeAdbGuideTab.set("remote"))},error:()=>{}}),window.addEventListener("focus",this.focusListener)}ngOnDestroy(){window.removeEventListener("focus",this.focusListener)}setTab(t){this.activeTab.set(t),t==="diagnostics"&&(this.systemService.fetchReadiness().subscribe(),this.systemService.fetchModelConfigEnv().subscribe())}setSelectedOs(t){this.selectedOs.set(t)}setAdbGuideTab(t){this.activeAdbGuideTab.set(t),this.remoteAdbError.set(null),this.remoteAdbMessage.set(null)}setEmulatorSetupMode(t){this.emulatorSetupMode.set(t)}setModelSetupMode(t){this.modelSetupMode.set(t),t==="custom"?(this.systemService.setSkipCredentialsCheck(!0),this.systemService.fetchModelConfigEnv().subscribe()):this.systemService.setSkipCredentialsCheck(!1)}toggleFullConfigFile(){this.showFullConfigFile.update(t=>!t)}toggleGeminiKeyVisibility(){this.showGeminiKey.update(t=>!t)}toggleOcrKeyVisibility(){this.showOcrKey.update(t=>!t)}toggleOcrConfig(){this.showOcrConfig.update(t=>!t)}onGeminiKeyChange(t){this.geminiKeyInput.set(t),this.isGeminiKeyEdited.set(!0),this.geminiSaveError.set(null),this.geminiSaveMessage.set(null)}onOcrKeyChange(t){this.ocrKeyInput.set(t),this.isOcrKeyEdited.set(!0),this.ocrSaveError.set(null),this.ocrSaveMessage.set(null)}saveGeminiKey(){let t=this.geminiKeyInput().trim();t&&(this.isSavingGeminiKey.set(!0),this.geminiSaveError.set(null),this.geminiSaveMessage.set(null),this.systemService.updateApiKey("google",t,!0).subscribe({next:e=>{this.isSavingGeminiKey.set(!1),this.isGeminiKeyEdited.set(!1),this.geminiSaveMessage.set(e?.message||"\u2713 Gemini API key verified & saved successfully."),setTimeout(()=>this.geminiSaveMessage.set(null),5e3)},error:e=>{this.isSavingGeminiKey.set(!1),this.geminiSaveError.set(e?.error?.detail||e?.message||"Failed to update Gemini API key.")}}))}clearGeminiKey(){this.geminiKeyInput.set(""),this.isGeminiKeyEdited.set(!1),this.geminiSaveError.set(null),this.geminiSaveMessage.set(null),this.savedGeminiKey().trim()?(this.isSavingGeminiKey.set(!0),this.systemService.updateApiKey("google","",!0).subscribe({next:t=>{this.isSavingGeminiKey.set(!1),this.geminiSaveMessage.set(t?.message||"\u2713 Gemini API key cleared."),setTimeout(()=>this.geminiSaveMessage.set(null),5e3)},error:t=>{this.isSavingGeminiKey.set(!1),this.geminiSaveError.set(t?.error?.detail||t?.message||"Failed to clear Gemini API key.")}})):(this.geminiSaveMessage.set("\u2713 Gemini API key cleared."),setTimeout(()=>this.geminiSaveMessage.set(null),3e3))}saveOcrKey(){let t=this.ocrKeyInput().trim();t&&(this.isSavingOcrKey.set(!0),this.ocrSaveError.set(null),this.ocrSaveMessage.set(null),this.systemService.updateApiKey("ocr",t,!0).subscribe({next:e=>{this.isSavingOcrKey.set(!1),this.isOcrKeyEdited.set(!1),this.ocrSaveMessage.set(e?.message||"\u2713 Vision OCR API key verified & saved."),setTimeout(()=>this.ocrSaveMessage.set(null),5e3)},error:e=>{this.isSavingOcrKey.set(!1),this.ocrSaveError.set(e?.error?.detail||e?.message||"Failed to update Vision OCR key.")}}))}clearOcrKey(){this.ocrKeyInput.set(""),this.isOcrKeyEdited.set(!1),this.ocrSaveError.set(null),this.ocrSaveMessage.set(null),this.savedOcrKey().trim()?(this.isSavingOcrKey.set(!0),this.systemService.updateApiKey("ocr","",!0).subscribe({next:t=>{this.isSavingOcrKey.set(!1),this.ocrSaveMessage.set(t?.message||"\u2713 Vision OCR API key cleared."),setTimeout(()=>this.ocrSaveMessage.set(null),5e3)},error:t=>{this.isSavingOcrKey.set(!1),this.ocrSaveError.set(t?.error?.detail||t?.message||"Failed to clear Vision OCR key.")}})):(this.ocrSaveMessage.set("\u2713 Vision OCR API key cleared."),setTimeout(()=>this.ocrSaveMessage.set(null),3e3))}testGeminiKey(){let t=this.geminiKeyInput().trim();t&&(this.isTestingGeminiKey.set(!0),this.geminiSaveError.set(null),this.geminiSaveMessage.set(null),this.systemService.testApiKey("google",t).subscribe({next:e=>{this.isTestingGeminiKey.set(!1),e?.valid?this.geminiSaveMessage.set(e?.message||"\u2713 Gemini API key is valid!"):this.geminiSaveError.set(e?.message||"Gemini API key verification failed."),setTimeout(()=>this.geminiSaveMessage.set(null),5e3)},error:e=>{this.isTestingGeminiKey.set(!1),this.geminiSaveError.set(e?.error?.detail||e?.message||"Gemini API key test failed.")}}))}testOcrKey(){let t=this.ocrKeyInput().trim();t&&(this.isTestingOcrKey.set(!0),this.ocrSaveError.set(null),this.ocrSaveMessage.set(null),this.systemService.testApiKey("ocr",t).subscribe({next:e=>{this.isTestingOcrKey.set(!1),e?.valid?this.ocrSaveMessage.set(e?.message||"\u2713 Vision OCR API key is valid!"):this.ocrSaveError.set(e?.message||"Vision OCR API key verification failed."),setTimeout(()=>this.ocrSaveMessage.set(null),5e3)},error:e=>{this.isTestingOcrKey.set(!1),this.ocrSaveError.set(e?.error?.detail||e?.message||"Vision OCR API key test failed.")}}))}getProviderDisplayName(t){switch(t){case"gemini":return"Gemini";case"ocr":return"Vision OCR";default:return t}}getProviderEnvVar(t){switch(t){case"gemini":return"GEMINI_API_KEY";case"ocr":return"GOOGLE_VISION_API_KEY";default:return"API_KEY"}}getProviderHint(t){switch(t){case"gemini":return"For a quick start, Google Gemini provides a free API key. Artemis also supports other models (OpenAI, Claude, OpenRouter, etc.)\u2014you can configure your own API keys directly in .env or your environment.";case"ocr":return"Google Cloud Vision API key for on-screen OCR text detection and UI grounding.";default:return"Configure your API key or use environment definitions."}}skipCredentialsCheck(){this.systemService.skipCredentialsCheck()}getApiKeyPlaceholder(t){switch(t){case"gemini":return"Enter Gemini API Key (e.g. AIzaSy...)";case"ocr":return"Enter Vision OCR API Key (e.g. AIzaSy...)";default:return"Enter API Key..."}}isTabProviderActive(t){if(t==="ocr")return this.isOcrConfigured();let e=t==="gemini"?"google":t;return this.configuredLlmProviders().some(i=>i.provider===e)}refreshReadiness(){this.isRefreshingDiagnostics.set(!0),this.systemService.fetchReadiness(!1,!0).subscribe({next:()=>{this.systemService.fetchModelConfigEnv().subscribe({next:()=>{setTimeout(()=>this.isRefreshingDiagnostics.set(!1),450)},error:()=>this.isRefreshingDiagnostics.set(!1)})},error:()=>this.isRefreshingDiagnostics.set(!1)})}restartAdbServer(){this.adbRestartFeedback.set(null),this.systemService.restartAdb().subscribe({next:t=>{this.adbRestartFeedback.set(t?.restart_result?.skipped?"Devices Refreshed \u2713":"ADB Refreshed \u2713"),setTimeout(()=>this.adbRestartFeedback.set(null),2500)},error:()=>{this.adbRestartFeedback.set("Restart Failed"),setTimeout(()=>this.adbRestartFeedback.set(null),3e3)}})}connectWifiDevice(){if(this.isRemoteAdbServer()){this.wifiConnectError.set("Switch to local ADB before connecting a Wireless ADB device.");return}let t=this.wifiHost().trim(),e=this.wifiPort().trim()||"5555",o=parseInt(e,10)||5555;if(!t){this.wifiConnectError.set("Please enter a valid IP address.");return}this.isConnectingWifi.set(!0),this.wifiConnectError.set(null),this.wifiConnectMessage.set(null),this.systemService.connectWirelessAdb(t,o).subscribe({next:i=>{this.isConnectingWifi.set(!1);let r=i?.connect_result;r?.success?(this.wifiConnectMessage.set(`Connected to ${t}:${o}!`),setTimeout(()=>this.wifiConnectMessage.set(null),4e3)):this.wifiConnectError.set(r?.message||"Connection failed. Please check phone IP & Wi-Fi.")},error:i=>{this.isConnectingWifi.set(!1),this.wifiConnectError.set(i?.error?.detail||"Failed to connect. Please check adb connection.")}})}connectRemoteAdbServer(){let t=this.remoteAdbHost().trim(),e=Number(this.remoteAdbPort().trim());if(!t){this.remoteAdbError.set("Enter the host name or IP address of the ADB server.");return}if(!Number.isInteger(e)||e<1||e>65535){this.remoteAdbError.set("Enter a port between 1 and 65535.");return}this.isConnectingRemoteAdb.set(!0),this.remoteAdbError.set(null),this.remoteAdbMessage.set(null),this.remoteAdbDevices.set([]),this.remoteAdbProbeResult.set(null),this.systemService.probeAdbServer(t,e).subscribe({next:o=>{this.isConnectingRemoteAdb.set(!1);let i=o.connection_result;i.success?(this.remoteAdbProbeResult.set(i),this.remoteAdbDevices.set(i.devices||[]),this.remoteAdbMessage.set(i.message)):this.remoteAdbError.set(i.message)},error:o=>{this.isConnectingRemoteAdb.set(!1),this.remoteAdbError.set(o?.error?.detail||"Unable to test the ADB server endpoint.")}})}activateRemoteAdbServer(){let t=this.remoteAdbProbeResult();if(!t?.success){this.remoteAdbError.set("Test the endpoint before using it.");return}this.isActivatingRemoteAdb.set(!0),this.remoteAdbError.set(null),this.systemService.connectAdbServer(t.endpoint.host,t.endpoint.port,this.rememberRemoteAdb()).subscribe({next:e=>{this.isActivatingRemoteAdb.set(!1);let o=e.connection_result;o.success?(this.remoteAdbProbeResult.set(o),this.remoteAdbDevices.set(o.devices||[]),this.remoteAdbMessage.set(o.message)):this.remoteAdbError.set(o.message)},error:e=>{this.isActivatingRemoteAdb.set(!1),this.remoteAdbError.set(e?.error?.detail||"Unable to use the ADB server endpoint.")}})}updateRemoteAdbHost(t){this.remoteAdbHost.set(t),this.clearRemoteAdbProbe()}updateRemoteAdbPort(t){this.remoteAdbPort.set(t),this.clearRemoteAdbProbe()}clearRemoteAdbProbe(){this.remoteAdbProbeResult.set(null),this.remoteAdbDevices.set([]),this.remoteAdbMessage.set(null),this.remoteAdbError.set(null)}switchToLocalAdbServer(){this.isSwitchingToLocalAdb.set(!0),this.remoteAdbError.set(null),this.systemService.useLocalAdbServer(!0).subscribe({next:t=>{this.isSwitchingToLocalAdb.set(!1),this.remoteAdbDevices.set([]),this.remoteAdbMessage.set(t.connection_result.message)},error:t=>{this.isSwitchingToLocalAdb.set(!1),this.remoteAdbError.set(t?.error?.detail||"Unable to switch back to the local ADB server.")}})}toggleConnectionMethods(){this.showConnectionMethods.update(t=>!t)}launchAvdEmulator(t){this.isRemoteAdbServer()||this.systemService.launchEmulator(t).subscribe()}toggleLaunchLogs(){this.showLaunchLogs.update(t=>!t)}stopEmulator(){this.systemService.stopEmulator().subscribe()}dismissEmulatorStatus(){this.systemService.dismissEmulatorStatus().subscribe()}selectTargetDevice(t){this.systemService.selectDevice(t).subscribe()}getEmulatorCommand(t){let e=this.emulatorPath();return`${this.isEmulatorInPath()?"emulator":e||"~/Android/Sdk/emulator/emulator"} -avd ${t}`}copyToClipboard(t,e){navigator.clipboard&&navigator.clipboard.writeText(t).then(()=>{this.copiedId.set(e),setTimeout(()=>{this.copiedId()===e&&this.copiedId.set(null)},2e3)})}setProfile(t){this.selectedProfile.set(t)}setCategory(t){this.selectedCategory.set(t)}shuffleSuggestions(){this.shuffleOffset.update(t=>t+3)}getAppNamesDisplay(t){return t.map(e=>e.name).join(" + ")}applySuggestion(t){this.taskGoal.set(t.goal),this.selectedProfile.set(t.profile),this.errorMessage.set(null)}applyQuickPrompt(t,e){this.taskGoal.set(t),e&&this.selectedProfile.set(e),this.errorMessage.set(null)}proceedToLauncher(){this.activeTab.set("launcher")}runTask(){let t=this.taskGoal().trim();if(!t){this.errorMessage.set("Please enter a task goal before running.");return}if(!this.isReady()){this.errorMessage.set("System prerequisites are not satisfied. Please review System Setup first."),this.activeTab.set("diagnostics");return}this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.runTask(t,this.selectedProfile(),this.selectedProfile()==="pro"&&this.expectedOutput().trim()?this.expectedOutput().trim():void 0,this.selectedProfile()==="pro"?this.enableOutputter():void 0,this.selectedProfile()==="pro"?{verificationLevel:this.verificationLevel().id,explorerMode:this.explorerMode().id}:void 0).subscribe({next:()=>{this.isSubmitting.set(!1),this.router.navigate(["/workspace"])},error:e=>{console.error("Failed to submit task from home page:",e),this.isSubmitting.set(!1),this.errorMessage.set(e?.error?.detail||"Failed to submit task. Please check server connection.")}})}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-home"]],decls:26,vars:14,consts:[[1,"home-container"],[1,"aurora-background"],[1,"glow","glow-1"],[1,"glow","glow-2"],[1,"glow","glow-3"],[1,"glow","glow-pending"],[1,"glow","glow-ready"],[1,"glow","glow-pro"],[1,"home-content"],[1,"hero-section"],[1,"hero-title"],[1,"mode-tabs-container"],[1,"mode-tabs"],["type","button",1,"mode-tab-btn",3,"click"],[1,"material-symbols-outlined","tab-icon","icon-ready"],[1,"material-symbols-outlined","tab-icon"],[1,"tab-label"],[1,"material-symbols-outlined","tab-icon","icon-locked"],[1,"setup-guide-view"],[1,"task-launcher-view"],[1,"guide-card"],[1,"guide-card-header"],[1,"step-badge"],[1,"material-symbols-outlined","step-check-icon"],[1,"step-info"],[1,"step-title-row"],[1,"step-title"],[1,"step-desc"],["type","button","title","Re-check all system dependencies",1,"btn-header-action",3,"click","disabled"],[1,"material-symbols-outlined"],[1,"guide-card-body"],[1,"one-click-install-bar"],[1,"env-matrix-grid"],[1,"env-item-card"],[1,"item-header"],[1,"item-icon-box"],[1,"material-symbols-outlined","item-icon"],[1,"item-title-group"],[1,"item-name"],[1,"item-subtitle"],[1,"item-status"],[1,"status-dot"],[1,"item-body"],[1,"model-choice-cards-row"],[1,"choice-card",3,"click"],[1,"choice-card-top"],[1,"choice-radio"],[1,"radio-dot"],[1,"choice-icon-wrap"],["viewBox","0 0 24 24","fill","none",1,"brand-svg","gemini-svg"],["id","geminiChoiceGrad","x1","0%","y1","0%","x2","100%","y2","100%"],["offset","0%","stop-color","#4285F4"],["offset","50%","stop-color","#9B72CB"],["offset","100%","stop-color","#D96570"],["d","M12 2C12 7.523 7.523 12 2 12C7.523 12 12 16.477 12 22C12 16.477 16.477 12 22 12C16.477 12 12 7.523 12 2Z","fill","url(#geminiChoiceGrad)"],[1,"choice-text"],[1,"choice-title-row"],[1,"choice-title"],[1,"tag-badge","tag-rec"],[1,"choice-desc"],[1,"choice-icon-wrap","icon-gear"],[1,"custom-config-inspector"],["type","button",1,"btn-restart-adb","btn-connection-options",3,"click"],["type","button",1,"btn-restart-adb",3,"click","disabled","title"],[1,"connected-device-panel"],[1,"emulator-boot-tracker-container"],[1,"emulator-failed-container"],[1,"foolproof-guide-container"],[1,"device-locked-panel"],[1,"unauthorized-panel"],[1,"adb-missing-panel"],[1,"ready-banner-footer"],[1,"install-bar-left"],[1,"install-bar-icon-box"],[1,"material-symbols-outlined","bolt-icon"],[1,"install-bar-text"],[1,"install-bar-title-row"],[1,"install-bar-title"],[1,"os-selector-pills"],["type","button",1,"os-pill",3,"click"],[1,"install-bar-desc"],[1,"code-box","small-box"],["type","button",1,"btn-copy-code",3,"click"],[1,"item-action-box"],[1,"terminal-prefix"],[1,"code-text"],[1,"credentials-input-box"],[1,"input-box-header"],[1,"header-label"],[1,"material-symbols-outlined","label-icon"],["href","https://aistudio.google.com/app/apikey","target","_blank","rel","noopener noreferrer",1,"btn-header-link"],[1,"unified-key-row"],[1,"unified-key-wrapper"],[1,"material-symbols-outlined","key-prefix-icon"],["placeholder","Enter Gemini API Key (e.g. AIzaSy...)","spellcheck","false","autocomplete","off",1,"unified-key-input",3,"ngModelChange","type","ngModel"],["type","button",1,"btn-icon-action",3,"click","title"],["type","button","title","Copy API Key",1,"btn-icon-action"],["type","button",1,"btn-unified-save",3,"click","disabled"],["type","button","title","Test if Gemini API key is valid",1,"btn-unified-test",3,"click","disabled"],[1,"feedback-msg","success"],[1,"feedback-msg","error"],[1,"ocr-enhancement-card"],[1,"ocr-card-toggle-bar",3,"click"],[1,"toggle-bar-left"],[1,"material-symbols-outlined","icon-ocr"],[1,"toggle-bar-titles"],[1,"ocr-main-title"],[1,"toggle-bar-right"],["title","OCR Configured",1,"status-dot","dot-pass"],[1,"material-symbols-outlined","caret-icon"],[1,"ocr-content-body"],["type","button","title","Copy API Key",1,"btn-icon-action",3,"click"],[1,"material-symbols-outlined","spin"],[1,"ocr-help-desc"],[1,"credentials-input-box","ocr-box"],["placeholder","Enter Vision OCR API Key (e.g. AIzaSy...)","spellcheck","false","autocomplete","off",1,"unified-key-input",3,"ngModelChange","type","ngModel"],["type","button","title","Test if Vision OCR key is valid",1,"btn-unified-test",3,"click","disabled"],[1,"inspector-card"],[1,"inspector-card-header"],[1,"inspector-header-left"],[1,"material-symbols-outlined","header-icon"],[1,"inspector-header-titles"],[1,"inspector-title"],[1,"inspector-subtitle"],[1,"inspector-header-actions"],["type","button","title","Copy file path",1,"btn-file-action",3,"click"],["type","button",1,"btn-file-action","btn-toggle-code",3,"click"],[1,"inspector-summary-row"],[1,"summary-metric"],[1,"metric-label"],[1,"metric-val-pill"],[1,"provider-dot"],[1,"provider-name"],[1,"model-divider"],[1,"model-name"],[1,"jsonc-viewer-box"],[1,"env-keys-table"],[1,"env-key-row",3,"is-configured"],[1,"metric-val-pill","secondary"],[1,"jsonc-code"],[1,"env-key-row"],[1,"env-name-col"],[1,"env-key-name"],[1,"env-status-col"],[1,"badge-env-pass"],[1,"badge-env-unset"],[1,"key-preview"],[1,"material-symbols-outlined","spinning"],[1,"material-symbols-outlined","feedback-done"],[1,"feedback-text"],[1,"device-hero-card"],[1,"device-avatar"],[1,"material-symbols-outlined","device-hero-icon"],[1,"device-hero-details"],[1,"device-hero-name"],[1,"device-hero-meta"],[1,"device-hero-status"],[1,"status-dot","dot-pass"],[1,"status-text"],[1,"multi-device-switcher"],[1,"meta-dot"],[1,"remote-endpoint-label"],[1,"switcher-label"],[1,"switcher-chips"],["type","button",1,"device-chip-btn",3,"selected"],["type","button",1,"device-chip-btn",3,"click"],[1,"material-symbols-outlined","chip-icon"],[1,"active-check"],[1,"emulator-boot-tracker-card"],[1,"tracker-header"],[1,"tracker-title-group"],[1,"tracker-icon-box"],[1,"material-symbols-outlined","spinning-icon"],[1,"tracker-titles"],[1,"tracker-main-heading"],[1,"tracker-title"],[1,"avd-highlight"],[1,"stage-tag-badge"],[1,"tracker-subtitle"],[1,"tracker-actions"],["type","button",1,"btn-toggle-logs",3,"click"],["type","button","title","Terminate emulator process",1,"btn-stop-emu",3,"click"],[1,"boot-stepper"],[1,"step-item"],[1,"step-circle"],[1,"material-symbols-outlined","check-icon"],[1,"spinner-tiny"],[1,"step-label"],[1,"step-line"],[1,"progress-bar-container"],[1,"progress-bar-track"],[1,"progress-bar-fill"],[1,"progress-bar-meta"],[1,"progress-percent"],[1,"progress-timer"],[1,"material-symbols-outlined","timer-icon"],[1,"boot-tip-banner"],[1,"material-symbols-outlined","tip-icon"],[1,"boot-logs-drawer"],[1,"logs-header"],[1,"logs-header-left"],["type","button",1,"btn-copy-logs",3,"click"],[1,"logs-body"],[1,"log-empty"],[1,"log-line"],[1,"emulator-failed-card"],[1,"failed-header"],[1,"failed-header-left"],[1,"failed-icon-box"],[1,"material-symbols-outlined","failed-icon"],[1,"failed-titles"],[1,"failed-title"],[1,"failed-desc"],["type","button","title","Dismiss error",1,"btn-dismiss-failed",3,"click"],[1,"failed-logs-box"],[1,"failed-actions-bar"],["type","button",1,"btn-retry-launch",3,"disabled"],["type","button",1,"btn-restart-adb-sub",3,"click"],["type","button",1,"btn-dismiss-flat",3,"click"],[1,"failed-logs-pre"],["type","button",1,"btn-retry-launch",3,"click","disabled"],[1,"connection-methods-nav"],["type","button",1,"method-nav-card",3,"click"],[1,"method-card-left"],[1,"method-icon-box"],[1,"method-info"],[1,"method-title-row"],[1,"method-title"],[1,"method-desc"],[1,"method-card-right"],[1,"radio-indicator"],[1,"active-remote-notice"],[1,"method-details-panel"],[1,"method-details-panel","remote-adb-panel"],[1,"active-remote-copy"],["type","button",1,"btn-use-local-adb",3,"click","disabled"],[1,"avds-section"],[1,"emulator-setup-guide-container"],[1,"section-header-bar"],[1,"section-title-group"],[1,"section-title"],[1,"count-badge"],[1,"avds-list"],[1,"avd-card-item"],[1,"avd-item-main"],[1,"avd-avatar"],[1,"avd-meta"],[1,"avd-heading"],[1,"avd-name"],[1,"avd-type-pill"],[1,"avd-item-actions"],["type","button",1,"btn-avd-launch",3,"click","disabled","title"],[1,"spinner-icon"],[1,"material-symbols-outlined","action-icon"],[1,"empty-avd-banner"],[1,"banner-left"],[1,"empty-avd-icon-box"],[1,"banner-titles"],[1,"banner-title-row"],[1,"empty-title"],[1,"empty-desc"],[1,"mode-switch-pills"],["type","button",1,"pill-btn",3,"click"],[1,"material-symbols-outlined","pill-icon"],[1,"steps-flow-grid","emu-flow-grid"],[1,"cli-steps-list"],[1,"flow-step-card"],[1,"step-card-header"],[1,"step-number-badge"],[1,"material-symbols-outlined","step-card-icon"],[1,"step-card-title"],[1,"step-card-desc"],["href","https://developer.android.com/studio","target","_blank","rel","noopener",1,"guide-link"],[1,"cli-step-item"],[1,"step-badge-num"],[1,"step-content"],[1,"step-text"],[1,"usb-guide-intro"],[1,"material-symbols-outlined","intro-icon"],[1,"steps-flow-grid"],[1,"wifi-guide-intro"],[1,"wifi-interactive-box"],[1,"wifi-form-row"],[1,"wifi-input-col","ip-col"],[1,"wifi-label"],[1,"wifi-input-wrap"],[1,"material-symbols-outlined","wrap-icon"],["type","text","placeholder","e.g. 192.168.1.108","spellcheck","false",1,"wifi-input",3,"ngModelChange","ngModel"],[1,"wifi-input-col","port-col"],["type","text","placeholder","5555","spellcheck","false",1,"wifi-input",3,"ngModelChange","ngModel"],[1,"wifi-btn-col"],["type","button",1,"btn-wifi-connect",3,"click","disabled"],[1,"wifi-status-alert","success"],[1,"wifi-status-alert","error"],[1,"info-callout-box"],[1,"material-symbols-outlined","callout-icon"],[1,"callout-text"],[1,"remote-guide-intro"],[1,"remote-adb-interactive-box"],[1,"remote-adb-form-row"],[1,"remote-adb-input-col","host-col"],["for","remote-adb-host",1,"remote-adb-label"],[1,"remote-adb-input-wrap"],["id","remote-adb-host","type","text","placeholder","127.0.0.1","autocomplete","off","spellcheck","false",1,"remote-adb-input",3,"ngModelChange","ngModel"],[1,"remote-adb-input-col","port-col"],["for","remote-adb-port",1,"remote-adb-label"],["id","remote-adb-port","type","text","inputmode","numeric","placeholder","5038","autocomplete","off","spellcheck","false",1,"remote-adb-input",3,"ngModelChange","ngModel"],[1,"remote-adb-btn-col"],["type","button",1,"btn-remote-adb-connect",3,"click","disabled"],[1,"remember-remote-adb"],["type","checkbox",3,"ngModelChange","ngModel"],[1,"remote-adb-status-alert","success"],["role","alert",1,"remote-adb-status-alert","error"],[1,"remote-device-results"],["type","button",1,"btn-remote-adb-connect","btn-use-tested-endpoint",3,"disabled"],[1,"remote-adb-explanation"],[1,"remote-device-row"],[1,"remote-device-copy"],[1,"remote-device-state"],["type","button",1,"btn-remote-adb-connect","btn-use-tested-endpoint",3,"click","disabled"],[1,"device-locked-card"],[1,"material-symbols-outlined","alert-icon"],[1,"locked-details"],[1,"locked-title"],[1,"locked-desc"],["type","button",1,"btn-check-unlock",3,"click","disabled"],[1,"multi-device-switcher",2,"margin-top","12px"],[1,"unauthorized-card"],[1,"unauth-details"],[1,"unauth-title"],[1,"unauth-desc"],["type","button",1,"btn-retry-auth",3,"click"],[1,"error-msg-box"],[1,"material-symbols-outlined","err-icon"],[1,"install-commands-row"],[1,"install-card"],[1,"install-os"],[1,"detected-badge"],[1,"ready-banner-left"],[1,"step-badge","step-pass"],[1,"ready-text-group"],[1,"ready-title"],[1,"ready-desc"],["type","button",1,"btn-proceed-launch",3,"click"],[1,"material-symbols-outlined","rocket-icon"],[1,"launcher-warning-bar"],[1,"composer-card"],[1,"intent-suggestion-strip"],[1,"composer-input-area"],["id","task-input-field","rows","3","placeholder","Describe what you want to achieve on your phone (e.g. Open Gmail and search for latest emails)...",1,"composer-textarea",3,"ngModelChange","keydown.control.enter","keydown.meta.enter","ngModel","disabled"],[1,"composer-model-descriptor"],[1,"descriptor-icon-wrap"],[1,"descriptor-body"],[1,"desc-line"],[1,"error-banner"],[1,"composer-action-bar"],[1,"bar-left"],[1,"model-toggle-capsule"],["type","button","title","Flash: fast single-look loop for everyday app tasks (3\u20135s/step)",1,"model-toggle-btn","btn-flash",3,"click"],[1,"material-symbols-outlined","btn-icon"],[1,"btn-text"],["type","button","title","Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks (15\u201340s/step)",1,"model-toggle-btn","btn-pro",3,"click"],[1,"bar-right"],[1,"char-count"],["type","button",1,"btn-run-composer",3,"click","disabled","title"],[1,"smart-suggestions-container"],[1,"suggestions-header-bar"],[1,"header-left"],[1,"section-label"],[1,"material-symbols-outlined","section-icon"],[1,"label-text"],[1,"category-tabs-group"],["type","button",1,"cat-tab-btn",3,"click"],["type","button",1,"cat-tab-btn","cat-flash",3,"click"],[1,"material-symbols-outlined","tab-mini-icon"],["type","button",1,"cat-tab-btn","cat-pro",3,"click"],[1,"header-right"],["type","button","title","Discover more tasks",1,"btn-shuffle-suggestions",3,"click"],[1,"material-symbols-outlined","shuffle-icon"],[1,"suggestions-cards-grid"],[1,"suggestion-card",3,"is-pro-card","is-flash-card"],[1,"launcher-warning-bar",3,"click"],[1,"material-symbols-outlined","warn-icon"],[1,"material-symbols-outlined","arrow"],[1,"suggestion-left"],[1,"material-symbols-outlined","sparkle-icon"],[1,"suggestion-text"],["type","button",1,"btn-switch-pro",3,"click"],[1,"material-symbols-outlined","btn-arrow"],[1,"mode-name"],[1,"bullet"],[1,"limitation-tag"],[1,"material-symbols-outlined","error-icon"],[1,"spinner"],[1,"material-symbols-outlined","run-icon"],[1,"pro-outputter-standalone-card"],[1,"outputter-card-header"],[1,"outputter-title"],[1,"outputter-optional-tag"],[1,"outputter-input-wrapper"],["type","text","placeholder","Specify expected output or schema (e.g. 'Extract SMS verification code as JSON' or 'Summary of discounts and total price')...","spellcheck","false",1,"outputter-standalone-input",3,"ngModelChange","ngModel"],["type","button","title","Clear expected output",1,"btn-clear-standalone"],[1,"pro-tuning-card"],[1,"tuning-card-header"],[1,"tuning-title"],["type","button","title","Back to the defaults",1,"btn-tuning-reset"],[1,"tuning-sliders"],[1,"tuning-slider",3,"verify","explore","dragging","--%NS%pct"],["type","button","title","Clear expected output",1,"btn-clear-standalone",3,"click"],["type","button","title","Back to the defaults",1,"btn-tuning-reset",3,"click"],[1,"tuning-slider"],[1,"slider-head"],[1,"slider-name"],[1,"slider-value"],["aria-hidden","true",1,"slider-ends"],[1,"slider-track-wrap",3,"pointerenter","pointerleave"],["aria-hidden","true",1,"slider-rail"],[1,"slider-fill"],[1,"slider-notch",3,"reached","--%NS%n"],["aria-hidden","true",1,"slider-thumb"],["aria-hidden","true",1,"thumb-ripple"],["type","range","min","0","step","1",1,"slider-input",3,"input","focus","blur","max","value"],[1,"slider-notch"],[1,"slider-solid"],["width","100%","height","6","aria-hidden","true",1,"slider-dither"],["width","2","height","2",3,"opacity","animation-delay"],["width","2","height","2"],["aria-hidden","true",1,"tuning-tip-arrow"],["role","tooltip",1,"tuning-tip"],[1,"tip-heading"],[1,"tip-title"],[1,"tip-latency"],[1,"tip-tagline"],[1,"tip-section"],[1,"tip-section-title"],[1,"tip-section","skips"],[1,"tip-best"],[1,"suggestion-card",3,"click"],[1,"card-top-row"],[1,"app-icons-cluster"],[1,"app-icon-badge",3,"title"],[1,"app-names-label"],[1,"mode-badge",3,"title"],[1,"card-body"],[1,"card-title"],[1,"card-desc"],[1,"card-footer-row"],[1,"tag-pill"],[1,"use-prompt-action"],[1,"material-symbols-outlined","action-arrow"]],template:function(e,o){e&1&&(l(0,"div",0)(1,"div",1),$(2,"div",2)(3,"div",3)(4,"div",4)(5,"div",5)(6,"div",6)(7,"div",7),d(),l(8,"main",8)(9,"section",9)(10,"h1",10),u(11," What would you like to do on your phone? "),d()(),l(12,"div",11)(13,"div",12)(14,"button",13),k("click",function(){return o.setTab("diagnostics")}),M(15,MD,2,0,"span",14)(16,bD,2,0,"span",15),l(17,"span",16),u(18,"System Setup & Prerequisites"),d()(),l(19,"button",13),k("click",function(){return o.setTab("launcher")}),M(20,PD,2,0,"span",17)(21,OD,2,0,"span",15),l(22,"span",16),u(23,"Launch Autonomous Task"),d()()()(),M(24,_T,155,87,"section",18),M(25,jT,74,32,"section",19),d()()),e&2&&(w("system-ready",o.isReady())("system-pending",!o.isReady())("pro-theme-active",o.selectedProfile()==="pro"),g(14),w("active",o.activeTab()==="diagnostics"),g(),b(o.isReady()?15:16),g(4),w("active",o.activeTab()==="launcher"),g(),b(o.isReady()?21:20),g(4),b(o.activeTab()==="diagnostics"?24:-1),g(),b(o.activeTab()==="launcher"?25:-1))},dependencies:[Qt,zr,em,Nd,ws],styles:['@charset "UTF-8";.os-selector-pills[_ngcontent-%COMP%]{display:inline-flex;align-items:center;padding:2px;gap:2px;background:#ffffffa6;border:1px solid rgba(226,232,240,.85);border-radius:8px;box-shadow:0 1px 3px #0f172a0a,inset 0 1px 1px #fffc;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%)}.os-selector-pills[_ngcontent-%COMP%] .os-pill[_ngcontent-%COMP%]{padding:3px 9px;border:0;border-radius:6px;background:transparent;color:#475569;font-family:inherit;font-size:11px;font-weight:600;line-height:1.35;cursor:pointer;transition:color .15s ease,background-color .15s ease,box-shadow .15s ease}.os-selector-pills[_ngcontent-%COMP%] .os-pill[_ngcontent-%COMP%]:hover{color:#0f172a;background:#ffffffb8}.os-selector-pills[_ngcontent-%COMP%] .os-pill.active[_ngcontent-%COMP%]{color:#fff;background:#1a73e8;box-shadow:0 1px 2px #0f172a1f}.os-selector-pills[_ngcontent-%COMP%] .os-pill[_ngcontent-%COMP%]:focus-visible{outline:2px solid rgba(26,115,232,.35);outline-offset:1px}[_nghost-%COMP%]{display:block;min-height:100vh;width:100%;background-color:#f8f9fa;font-family:Google Sans Text,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;color:#0f172a;box-sizing:border-box;-webkit-font-smoothing:antialiased}*[_ngcontent-%COMP%], *[_ngcontent-%COMP%]:before, *[_ngcontent-%COMP%]:after{box-sizing:border-box}.home-container[_ngcontent-%COMP%]{position:relative;min-height:100vh;display:flex;flex-direction:column;background-color:#f8f9fa;overflow-x:hidden}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%]{display:block;position:fixed;top:0;left:0;width:100vw;height:100vh;pointer-events:none;z-index:1;overflow:hidden;opacity:.9;transition:opacity .8s ease}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow[_ngcontent-%COMP%]{position:absolute;border-radius:50%;transform:translateZ(0);will-change:transform,opacity;pointer-events:none}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-1[_ngcontent-%COMP%]{top:-140px;left:10%;width:520px;height:520px;background:radial-gradient(circle,rgba(59,130,246,.12) 0%,rgba(99,102,241,.04) 45%,transparent 82%);animation:_ngcontent-%COMP%_floatGlow1 22s ease-in-out infinite alternate}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-2[_ngcontent-%COMP%]{top:-100px;right:12%;width:560px;height:560px;background:radial-gradient(circle,rgba(147,51,234,.08) 0%,rgba(219,39,119,.03) 45%,transparent 82%);animation:_ngcontent-%COMP%_floatGlow2 26s ease-in-out infinite alternate}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-3[_ngcontent-%COMP%]{top:280px;left:30%;width:460px;height:460px;background:radial-gradient(circle,rgba(6,182,212,.08) 0%,rgba(59,130,246,.03) 45%,transparent 82%);animation:_ngcontent-%COMP%_floatGlow3 20s ease-in-out infinite alternate}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-pending[_ngcontent-%COMP%]{top:-120px;left:15%;right:15%;margin:0 auto;width:720px;height:520px;background:radial-gradient(circle,rgba(245,158,11,.09) 0%,rgba(217,119,6,.035) 38%,transparent 82%);opacity:0;transition:opacity .8s cubic-bezier(.4,0,.2,1);animation:_ngcontent-%COMP%_readyPulseGlow 9s ease-in-out infinite alternate;pointer-events:none}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-ready[_ngcontent-%COMP%]{top:-120px;left:15%;right:15%;margin:0 auto;width:700px;height:520px;background:radial-gradient(circle,rgba(16,185,129,.14) 0%,rgba(6,182,212,.08) 38%,transparent 82%);opacity:0;transition:opacity 1s cubic-bezier(.4,0,.2,1);animation:_ngcontent-%COMP%_readyPulseGlow 7s ease-in-out infinite alternate;pointer-events:none}.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-pro[_ngcontent-%COMP%]{top:-100px;left:20%;right:20%;margin:0 auto;width:680px;height:520px;background:radial-gradient(circle,rgba(99,102,241,.18) 0%,rgba(147,51,234,.08) 38%,transparent 82%);opacity:0;transition:opacity .8s cubic-bezier(.4,0,.2,1);pointer-events:none}.home-container.system-pending[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-pending[_ngcontent-%COMP%]{opacity:1}.home-container.system-pending[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-1[_ngcontent-%COMP%]{background:radial-gradient(circle,rgba(245,158,11,.07) 0%,rgba(99,102,241,.03) 55%,transparent 75%)}.home-container.system-ready[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-pending[_ngcontent-%COMP%]{opacity:0}.home-container.system-ready[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-ready[_ngcontent-%COMP%]{opacity:1}.home-container.system-ready[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-1[_ngcontent-%COMP%]{background:radial-gradient(circle,rgba(16,185,129,.1) 0%,rgba(59,130,246,.05) 55%,transparent 75%)}.home-container.pro-theme-active[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-pro[_ngcontent-%COMP%]{opacity:1}.home-container.pro-theme-active[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-ready[_ngcontent-%COMP%]{opacity:.35}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%]{position:relative;z-index:5;max-width:960px;width:100%;margin:0 auto;padding:36px 24px 60px;display:flex;flex-direction:column;gap:18px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .hero-section[_ngcontent-%COMP%]{text-align:center;padding:16px 0 6px;margin:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .hero-section[_ngcontent-%COMP%] .hero-title[_ngcontent-%COMP%]{font-family:Outfit,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:32px;font-weight:600;letter-spacing:-.035em;line-height:1.2;color:#0f172a;margin:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .hero-section[_ngcontent-%COMP%] .hero-subtitle[_ngcontent-%COMP%]{font-size:14.5px;color:#64748b;font-weight:400;margin:8px 0 0;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%]{display:flex;justify-content:center;margin-bottom:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%]{display:grid;grid-template-columns:1fr 1fr;width:100%;max-width:520px;box-sizing:border-box;background:#ffffffb8;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);padding:3px;border-radius:9999px;border:1px solid rgba(226,232,240,.85);gap:3px;box-shadow:0 2px 8px -1px #0f172a0f,0 1px 2px #00000005,inset 0 1px 1px #ffffffe6;-webkit-user-select:none;user-select:none;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:background .25s cubic-bezier(.4,0,.2,1),box-shadow .25s cubic-bezier(.4,0,.2,1),border-color .25s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%]:hover{background:#ffffffd9;box-shadow:0 4px 14px -2px #0f172a14,0 2px 4px #00000005,inset 0 1px 1px #fff;border-color:#cbd5e1e6}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:7px;height:32px;padding:0 14px;box-sizing:border-box;border:none;background:transparent;border-radius:9999px;cursor:pointer;font-family:inherit;font-size:13px;font-weight:600;color:#475569;line-height:1;transition:all .2s cubic-bezier(.4,0,.2,1);outline:none;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{font-size:16px;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:#64748b;transition:color .15s ease,transform .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%] .tab-icon.icon-ready[_ngcontent-%COMP%]{color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%] .tab-icon.icon-locked[_ngcontent-%COMP%]{color:#94a3b8;font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%]{color:#475569;transition:color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]:hover:not(.active){background:#ffffffb3;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]:hover:not(.active) .tab-icon[_ngcontent-%COMP%]{color:#1e293b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]:hover:not(.active) .tab-icon.icon-ready[_ngcontent-%COMP%]{color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]:hover:not(.active) .tab-label[_ngcontent-%COMP%]{color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn.active[_ngcontent-%COMP%]{background:#fff!important;color:#0f172a!important;font-weight:700;box-shadow:0 2px 8px -1px #0f172a14,0 1px 2px #0000000a,inset 0 1px #fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn.active[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn.active[_ngcontent-%COMP%] .tab-icon.icon-ready[_ngcontent-%COMP%]{color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn.active[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%]{color:#0f172a!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .mode-tabs-container[_ngcontent-%COMP%] .mode-tabs[_ngcontent-%COMP%] .mode-tab-btn[_ngcontent-%COMP%]:active{transform:scale(.98)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:16px;animation:_ngcontent-%COMP%_fadeIn .2s ease-out}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0;border-radius:14px;box-shadow:0 1px 3px #00000008,0 4px 12px #00000005;overflow:hidden;transition:border-color .2s ease,box-shadow .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%]:hover{border-color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:16px 20px;border-bottom:1px solid #f1f5f9;background:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-badge[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;font-size:13.5px;font-weight:700;background:#f1f5f9;color:#475569;flex-shrink:0;transition:all .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-badge[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-badge[_ngcontent-%COMP%] .step-check-icon[_ngcontent-%COMP%]{font-size:18px;font-weight:700;color:inherit}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-badge.step-pass[_ngcontent-%COMP%]{background:#ecfdf5;color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%]{flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;margin-bottom:2px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%] .step-title[_ngcontent-%COMP%]{margin:0;font-size:15px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%] .status-pill[_ngcontent-%COMP%]{font-size:11px;font-weight:600;padding:2px 8px;border-radius:12px;line-height:1.3;height:22px;display:inline-flex;align-items:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%] .status-pill.pill-pass[_ngcontent-%COMP%]{background:#ecfdf5;color:#059669;border:1px solid #a7f3d0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%] .status-pill.pill-warn[_ngcontent-%COMP%]{background:#fffbeb;color:#b45309;border:1px solid #fde68a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-title-row[_ngcontent-%COMP%] .status-pill.pill-fail[_ngcontent-%COMP%]{background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .step-info[_ngcontent-%COMP%] .step-desc[_ngcontent-%COMP%]{margin:0;font-size:12.5px;color:#475569;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:0 13px;height:32px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.88);box-shadow:0 2px 8px -1px #0f172a0d,inset 0 1px 1px #fffffff2;color:#475569;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]:hover:not(:disabled){background:#ffffffeb;border-color:#bfdbfef2;color:#1a73e8;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done){color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%]:active:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%]{border-radius:8px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px;color:#64748b;transition:transform .2s ease,color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%] .feedback-done[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%] .feedback-done[_ngcontent-%COMP%]{color:#059669!important;font-size:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%] .feedback-text[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%] .feedback-text[_ngcontent-%COMP%]{color:#059669;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-restart-adb[_ngcontent-%COMP%] .spinning[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-header[_ngcontent-%COMP%] .btn-header-action[_ngcontent-%COMP%] .spinning[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_spin 1s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .guide-card[_ngcontent-%COMP%] .guide-card-body[_ngcontent-%COMP%]{padding:18px 20px;display:flex;flex-direction:column;gap:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%]{background:#f0f9ff;border:1px solid #bae6fd;border-radius:10px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between;gap:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-icon-box[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:8px;background:#0284c7;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-icon-box[_ngcontent-%COMP%] .bolt-icon[_ngcontent-%COMP%]{font-size:18px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%]{display:flex;align-items:center;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%] .install-bar-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%] .os-selector-pills[_ngcontent-%COMP%]{display:inline-flex;align-items:center;background:#ffffffa6;backdrop-filter:blur(12px) saturate(180%);-webkit-backdrop-filter:blur(12px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:8px;padding:2px;gap:2px;box-shadow:0 1px 3px #0f172a0a,inset 0 1px 1px #fffc}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%] .os-selector-pills[_ngcontent-%COMP%] .os-pill[_ngcontent-%COMP%]{border:none;background:transparent;font-size:11px;font-weight:600;color:#475569;padding:3px 9px;border-radius:6px;cursor:pointer;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%] .os-selector-pills[_ngcontent-%COMP%] .os-pill[_ngcontent-%COMP%]:hover{color:#0f172a;background:#ffffff80}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-title-row[_ngcontent-%COMP%] .os-selector-pills[_ngcontent-%COMP%] .os-pill.active[_ngcontent-%COMP%]{background:#1a73e8eb;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);color:#fff!important;box-shadow:0 2px 6px #1a73e84d,inset 0 1px 1px #ffffff59}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .install-bar-left[_ngcontent-%COMP%] .install-bar-text[_ngcontent-%COMP%] .install-bar-desc[_ngcontent-%COMP%]{font-size:12px;color:#475569;line-height:1.4;margin-top:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%]{margin:0;flex-shrink:0}@media(max-width:860px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .one-click-install-bar[_ngcontent-%COMP%]{flex-direction:column;align-items:stretch}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(2,1fr);gap:12px}@media(max-width:640px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%]{grid-template-columns:1fr}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;justify-content:center;gap:8px;transition:border-color .18s ease,box-shadow .18s ease;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%]:hover{border-color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-icon-box[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:8px;background:#f8fafc;border:1px solid #e2e8f0;color:#475569;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-icon-box[_ngcontent-%COMP%] .item-icon[_ngcontent-%COMP%]{font-size:17px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-title-group[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-title-group[_ngcontent-%COMP%] .item-name[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-title-group[_ngcontent-%COMP%] .item-subtitle[_ngcontent-%COMP%]{font-size:11.5px;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-status[_ngcontent-%COMP%]{display:flex;align-items:center;margin-left:auto;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-status[_ngcontent-%COMP%] .status-dot[_ngcontent-%COMP%]{width:7px;height:7px;border-radius:50%}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-status[_ngcontent-%COMP%] .status-dot.dot-pass[_ngcontent-%COMP%]{background:#10b981}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-status[_ngcontent-%COMP%] .status-dot.dot-fail[_ngcontent-%COMP%]{background:#ef4444}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-status[_ngcontent-%COMP%] .status-dot.dot-warn[_ngcontent-%COMP%]{background:#f59e0b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-body[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card[_ngcontent-%COMP%] .item-body[_ngcontent-%COMP%] .item-action-box[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card.item-fail[_ngcontent-%COMP%]{background:#fff8f8;border-color:#fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .env-matrix-grid[_ngcontent-%COMP%] .env-item-card.item-fail[_ngcontent-%COMP%] .item-icon-box[_ngcontent-%COMP%]{background:#fef2f2;color:#dc2626;border-color:#fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%]{border:1px solid #e2e8f0;border-radius:10px;padding:14px 16px;background:#f8fafc;cursor:pointer;transition:all .18s ease;-webkit-user-select:none;user-select:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-radio[_ngcontent-%COMP%]{margin-top:2px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-radio[_ngcontent-%COMP%] .radio-dot[_ngcontent-%COMP%]{display:block;width:16px;height:16px;border-radius:50%;border:1.5px solid #cbd5e1;background:#fff;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-radio[_ngcontent-%COMP%] .radio-dot.checked[_ngcontent-%COMP%]{border-color:#1a73e8;background:#1a73e8;box-shadow:inset 0 0 0 3px #fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-icon-wrap[_ngcontent-%COMP%]{width:34px;height:34px;border-radius:8px;background:#fff;border:1px solid #e2e8f0;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-icon-wrap[_ngcontent-%COMP%] .brand-svg[_ngcontent-%COMP%]{width:18px;height:18px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-icon-wrap.icon-gear[_ngcontent-%COMP%]{color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-icon-wrap.icon-gear[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%]{flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-title-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-title-row[_ngcontent-%COMP%] .choice-title[_ngcontent-%COMP%]{font-size:13.5px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-title-row[_ngcontent-%COMP%] .tag-badge[_ngcontent-%COMP%]{font-size:10.5px;font-weight:600;padding:1px 7px;border-radius:10px;line-height:1.3}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-title-row[_ngcontent-%COMP%] .tag-badge.tag-rec[_ngcontent-%COMP%]{background:#eff6ff;color:#1a73e8;border:1px solid #bfdbfe}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-title-row[_ngcontent-%COMP%] .tag-badge.tag-ready[_ngcontent-%COMP%]{background:#ecfdf5;color:#059669;border:1px solid #a7f3d0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-desc[_ngcontent-%COMP%]{margin:3px 0 0;font-size:12px;color:#475569;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%] .choice-card-top[_ngcontent-%COMP%] .choice-text[_ngcontent-%COMP%] .choice-desc[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;background:#fff;border:1px solid #e2e8f0;padding:1px 4px;border-radius:4px;color:#1557b0;font-size:11px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card[_ngcontent-%COMP%]:hover:not(.selected){border-color:#cbd5e1;background:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card.selected[_ngcontent-%COMP%]{border-color:#1a73e8;background:#fff;box-shadow:0 0 0 1px #1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card.selected[_ngcontent-%COMP%] .choice-icon-wrap[_ngcontent-%COMP%]{border-color:#bfdbfe}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .model-choice-cards-row[_ngcontent-%COMP%] .choice-card.selected[_ngcontent-%COMP%] .choice-icon-wrap.icon-gear[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:14px 16px;display:flex;flex-direction:column;gap:10px;animation:_ngcontent-%COMP%_fadeIn .15s ease-out}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box.ocr-box[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%] .header-label[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%] .header-label[_ngcontent-%COMP%] .label-icon[_ngcontent-%COMP%]{font-size:16px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%] .btn-header-link[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;font-size:11.5px;font-weight:600;color:#1a73e8;text-decoration:none;padding:3px 10px;height:26px;background:#eff6ffbf;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(191,219,254,.85);border-radius:6px;box-shadow:0 1px 3px #2563eb0f,inset 0 1px 1px #fffc;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%] .btn-header-link[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:13px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .input-box-header[_ngcontent-%COMP%] .btn-header-link[_ngcontent-%COMP%]:hover{background:#dbeafee6;color:#1557b0;border-color:#1a73e8;box-shadow:0 2px 6px #2563eb26;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%]{flex:1 1 260px;min-width:0;display:flex;align-items:center;background:#ffffffbf;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid #e2e8f0;border-radius:8px;padding:0 10px;height:36px;transition:border-color .15s ease,box-shadow .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%]:focus-within{border-color:#1a73e8;box-shadow:0 0 0 2px #1a73e81f}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .key-prefix-icon[_ngcontent-%COMP%]{font-size:16px;color:#94a3b8;margin-right:6px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .unified-key-input[_ngcontent-%COMP%]{flex:1;min-width:0;border:none;outline:none;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:12.5px;color:#0f172a;background:transparent;padding:4px 0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .unified-key-input.is-masked[_ngcontent-%COMP%]{letter-spacing:2px;font-weight:700;font-size:13px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .unified-key-input[_ngcontent-%COMP%]::placeholder{color:#94a3b8;font-family:Google Sans Text,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:12px;letter-spacing:normal;font-weight:400}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .btn-icon-action[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;width:26px;height:26px;background:transparent;border:none;border-radius:4px;color:#64748b;cursor:pointer;transition:all .15s ease;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .btn-icon-action[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .btn-icon-action[_ngcontent-%COMP%] .text-emerald[_ngcontent-%COMP%]{color:#059669!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .unified-key-wrapper[_ngcontent-%COMP%] .btn-icon-action[_ngcontent-%COMP%]:hover{background:#f1f5f9cc;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:0 18px;height:36px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%]{border-radius:8px;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-save[_ngcontent-%COMP%] .spin[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_spin 1s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:0 15px;height:36px;background:#eff6ffb3;backdrop-filter:blur(14px) saturate(180%);-webkit-backdrop-filter:blur(14px) saturate(180%);color:#1a73e8;border:1px solid rgba(191,219,254,.85);border-radius:8px;box-shadow:0 2px 6px -1px #2563eb14,inset 0 1px 1px #ffffffe6;font-family:inherit;font-size:12.5px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1);white-space:nowrap;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%]:hover:not(:disabled){background:#dbeafee6;border-color:#1a73e8;color:#1557b0;box-shadow:0 4px 12px -2px #2563eb26,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .unified-key-row[_ngcontent-%COMP%] .btn-unified-test[_ngcontent-%COMP%] .spin[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_spin 1s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .feedback-msg[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:12px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .feedback-msg[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .feedback-msg.success[_ngcontent-%COMP%]{background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .credentials-input-box[_ngcontent-%COMP%] .feedback-msg.error[_ngcontent-%COMP%]{background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden;margin-top:2px;transition:border-color .18s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#fff;cursor:pointer;-webkit-user-select:none;user-select:none;transition:background .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%]:hover{background:#f8fafc}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-left[_ngcontent-%COMP%] .icon-ocr[_ngcontent-%COMP%]{font-size:18px;color:#1a73e8;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-left[_ngcontent-%COMP%] .toggle-bar-titles[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-left[_ngcontent-%COMP%] .toggle-bar-titles[_ngcontent-%COMP%] .ocr-main-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-right[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-right[_ngcontent-%COMP%] .status-dot[_ngcontent-%COMP%]{width:7px;height:7px;border-radius:50%}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-right[_ngcontent-%COMP%] .status-dot.dot-pass[_ngcontent-%COMP%]{background:#10b981}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-card-toggle-bar[_ngcontent-%COMP%] .toggle-bar-right[_ngcontent-%COMP%] .caret-icon[_ngcontent-%COMP%]{font-size:18px;color:#64748b;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-content-body[_ngcontent-%COMP%]{padding:12px 14px;border-top:1px solid #f1f5f9;display:flex;flex-direction:column;gap:10px;background:#f8fafc;animation:_ngcontent-%COMP%_fadeIn .15s ease-out}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-content-body[_ngcontent-%COMP%] .ocr-help-desc[_ngcontent-%COMP%]{margin:0;font-size:12px;color:#475569;line-height:1.45}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ocr-enhancement-card[_ngcontent-%COMP%] .ocr-content-body[_ngcontent-%COMP%] .credentials-input-box.ocr-box[_ngcontent-%COMP%]{margin:0;padding:12px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px;animation:_ngcontent-%COMP%_fadeIn .15s ease-out}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0;border-radius:10px;overflow:hidden}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:10px 14px;background:#fff;border-bottom:1px solid #f1f5f9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%] .header-icon[_ngcontent-%COMP%]{font-size:18px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%] .inspector-header-titles[_ngcontent-%COMP%]{display:flex;flex-direction:column}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%] .inspector-header-titles[_ngcontent-%COMP%] .inspector-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%] .inspector-header-titles[_ngcontent-%COMP%] .inspector-subtitle[_ngcontent-%COMP%]{font-size:11.5px;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-left[_ngcontent-%COMP%] .inspector-header-titles[_ngcontent-%COMP%] .inspector-subtitle[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;background:#f1f5f9;padding:1px 4px;border-radius:4px;color:#1557b0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .inspector-header-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:0 10px;height:28px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.88);box-shadow:0 2px 8px -1px #0f172a0d,inset 0 1px 1px #fffffff2;color:#475569;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]:hover:not(:disabled){background:#ffffffeb;border-color:#bfdbfef2;color:#1a73e8;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done){color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:13px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action.btn-toggle-code[_ngcontent-%COMP%]{background:#eff6ffbf;color:#1a73e8;border-color:#bfdbfed9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-card-header[_ngcontent-%COMP%] .btn-file-action.btn-toggle-code[_ngcontent-%COMP%]:hover{background:#dbeafee6;color:#1557b0;border-color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:12px;padding:12px 14px;background:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-label[_ngcontent-%COMP%]{font-size:10.5px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:12px;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill[_ngcontent-%COMP%] .provider-dot[_ngcontent-%COMP%]{width:7px;height:7px;border-radius:50%;background:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill[_ngcontent-%COMP%] .provider-name[_ngcontent-%COMP%]{font-weight:700;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill[_ngcontent-%COMP%] .model-divider[_ngcontent-%COMP%]{color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill[_ngcontent-%COMP%] .model-name[_ngcontent-%COMP%]{font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .inspector-summary-row[_ngcontent-%COMP%] .summary-metric[_ngcontent-%COMP%] .metric-val-pill.secondary[_ngcontent-%COMP%]{background:#f8fafc;color:#475569;font-size:11.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .jsonc-viewer-box[_ngcontent-%COMP%]{border-top:1px solid #e2e8f0;background:#0f172a;max-height:260px;overflow-y:auto;padding:12px 14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .jsonc-viewer-box[_ngcontent-%COMP%] .jsonc-code[_ngcontent-%COMP%]{margin:0;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:11.5px;line-height:1.5;color:#e2e8f0;white-space:pre-wrap;word-break:break-all}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%]{display:flex;flex-direction:column}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px;padding:8px 14px;border-bottom:1px solid #f1f5f9;transition:background .12s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%]:last-child{border-bottom:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%]:hover{background:#f8fafc}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-name-col[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-name-col[_ngcontent-%COMP%] .env-key-name[_ngcontent-%COMP%]{font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:12px;font-weight:600;color:#0f172a;background:#f1f5f9;padding:2px 6px;border-radius:4px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%]{display:flex;align-items:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%] .badge-env-pass[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#059669;background:#ecfdf5;border:1px solid #a7f3d0;padding:2px 7px;border-radius:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%] .badge-env-pass[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:13px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%] .badge-env-pass[_ngcontent-%COMP%] .key-preview[_ngcontent-%COMP%]{font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:10.5px;color:#047857;margin-left:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%] .badge-env-unset[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:#94a3b8;background:#f8fafc;border:1px solid #e2e8f0;padding:2px 7px;border-radius:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .custom-config-inspector[_ngcontent-%COMP%] .inspector-card[_ngcontent-%COMP%] .env-keys-table[_ngcontent-%COMP%] .env-key-row[_ngcontent-%COMP%] .env-status-col[_ngcontent-%COMP%] .badge-env-unset[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:13px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%]{display:flex;align-items:center;gap:16px;padding:16px 20px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 1px 3px #00000005,0 4px 12px #00000005;transition:all .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%]:hover{border-color:#cbd5e1;box-shadow:0 2px 6px #0000000a,0 8px 18px #00000008}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-avatar[_ngcontent-%COMP%]{width:44px;height:44px;border-radius:10px;background:#f1f5f9;border:1px solid #e2e8f0;color:#334155;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-avatar[_ngcontent-%COMP%] .device-hero-icon[_ngcontent-%COMP%]{font-size:22px;color:#334155!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-details[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px;min-width:0;flex:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-details[_ngcontent-%COMP%] .device-hero-name[_ngcontent-%COMP%]{font-size:14.5px;font-weight:600;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-details[_ngcontent-%COMP%] .device-hero-meta[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;font-size:12px;color:#64748b;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-details[_ngcontent-%COMP%] .device-hero-meta[_ngcontent-%COMP%] .meta-dot[_ngcontent-%COMP%]{color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-details[_ngcontent-%COMP%] .device-hero-meta[_ngcontent-%COMP%] .remote-endpoint-label[_ngcontent-%COMP%]{color:#1a73e8;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:11px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-status[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:500;color:#047857;margin-left:auto;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .device-hero-card[_ngcontent-%COMP%] .device-hero-status[_ngcontent-%COMP%] .status-dot[_ngcontent-%COMP%]{width:7px;height:7px;border-radius:50%;background:#10b981;box-shadow:0 0 #10b981b3;animation:_ngcontent-%COMP%_statusDotPulse 2s infinite cubic-bezier(.4,0,.6,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-label[_ngcontent-%COMP%]{font-size:11px;font-weight:700;color:#64748b;text-transform:uppercase;letter-spacing:.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%] .device-chip-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:0 10px;height:30px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;font-family:inherit;font-size:11.5px;font-weight:600;color:#475569;cursor:pointer;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%] .device-chip-btn[_ngcontent-%COMP%] .chip-icon[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%] .device-chip-btn[_ngcontent-%COMP%] .active-check[_ngcontent-%COMP%]{color:#059669;font-size:10.5px;font-weight:700}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%] .device-chip-btn.selected[_ngcontent-%COMP%]{background:#ecfdf5;border-color:#059669;color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .connected-device-panel[_ngcontent-%COMP%] .multi-device-switcher[_ngcontent-%COMP%] .switcher-chips[_ngcontent-%COMP%] .device-chip-btn[_ngcontent-%COMP%]:hover:not(.selected){background:#f8fafc;border-color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-container[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px;margin-bottom:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:16px;padding:20px;background:linear-gradient(180deg,#fff,#f8fafc);border:1.5px solid rgba(37,99,235,.25);border-radius:12px;box-shadow:0 4px 16px #2563eb0f}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%]{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:12px;flex:1;min-width:260px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-icon-box[_ngcontent-%COMP%]{width:36px;height:36px;border-radius:10px;background:#2563eb1a;color:#1a73e8;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-icon-box[_ngcontent-%COMP%] .spinning-icon[_ngcontent-%COMP%]{font-size:20px;animation:_ngcontent-%COMP%_spin 1.2s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%] .tracker-main-heading[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%] .tracker-main-heading[_ngcontent-%COMP%] .tracker-title[_ngcontent-%COMP%]{margin:0;font-size:15px;font-weight:700;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%] .tracker-main-heading[_ngcontent-%COMP%] .tracker-title[_ngcontent-%COMP%] .avd-highlight[_ngcontent-%COMP%]{color:#1a73e8;font-family:JetBrains Mono,monospace,sans-serif}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%] .tracker-main-heading[_ngcontent-%COMP%] .stage-tag-badge[_ngcontent-%COMP%]{display:inline-flex;align-items:center;padding:2px 8px;border-radius:12px;background:#2563eb1f;color:#1a73e8;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-title-group[_ngcontent-%COMP%] .tracker-titles[_ngcontent-%COMP%] .tracker-subtitle[_ngcontent-%COMP%]{margin:0;font-size:13px;color:#475569;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;padding:6px 13px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.88);box-shadow:0 2px 8px -1px #0f172a0d,inset 0 1px 1px #fffffff2;color:#475569;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]:hover:not(:disabled){background:#ffffffeb;border-color:#bfdbfef2;color:#1a73e8;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done){color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%]{border-radius:6px;font-size:12px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-toggle-logs.active[_ngcontent-%COMP%]{background:#eff6ffe6;color:#1a73e8;border-color:#2563eb66;box-shadow:0 2px 6px #2563eb1f}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-stop-emu[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:6px 13px;background:#fef2f2bf;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(254,202,202,.85);border-radius:6px;color:#dc2626;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 1px 3px #dc26260f,inset 0 1px 1px #fffc;transition:all .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-stop-emu[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-stop-emu[_ngcontent-%COMP%]:hover{background:#fee2e2f2;border-color:#dc2626;box-shadow:0 4px 12px -2px #dc262626;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .tracker-header[_ngcontent-%COMP%] .tracker-actions[_ngcontent-%COMP%] .btn-stop-emu[_ngcontent-%COMP%]:active{transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;background:#fff;border:1px solid #e2e8f0;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:#64748b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item[_ngcontent-%COMP%] .step-circle[_ngcontent-%COMP%]{width:22px;height:22px;border-radius:50%;background:#f1f5f9;border:1.5px solid #e2e8f0;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:#64748b;transition:all .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item[_ngcontent-%COMP%] .step-circle[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{font-size:14px;color:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item[_ngcontent-%COMP%] .step-circle[_ngcontent-%COMP%] .spinner-tiny[_ngcontent-%COMP%]{width:11px;height:11px;border:2px solid rgba(37,99,235,.3);border-top-color:#1a73e8;border-radius:50%;animation:_ngcontent-%COMP%_spin .8s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item[_ngcontent-%COMP%] .step-label[_ngcontent-%COMP%]{white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item.active[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item.active[_ngcontent-%COMP%] .step-circle[_ngcontent-%COMP%]{border-color:#1a73e8;background:#2563eb1a;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item.done[_ngcontent-%COMP%]{color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-item.done[_ngcontent-%COMP%] .step-circle[_ngcontent-%COMP%]{border-color:#059669;background:#059669;color:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-line[_ngcontent-%COMP%]{flex:1;height:2px;background:#e2e8f0;margin:0 10px;border-radius:2px;transition:all .3s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-stepper[_ngcontent-%COMP%] .step-line.done[_ngcontent-%COMP%]{background:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-track[_ngcontent-%COMP%]{width:100%;height:8px;background:#e2e8f0;border-radius:6px;overflow:hidden}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-track[_ngcontent-%COMP%] .progress-bar-fill[_ngcontent-%COMP%]{height:100%;background:linear-gradient(90deg,#3b82f6,#10b981);border-radius:6px;transition:width .4s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-meta[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;font-size:12px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-meta[_ngcontent-%COMP%] .progress-percent[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-meta[_ngcontent-%COMP%] .progress-timer[_ngcontent-%COMP%]{display:flex;align-items:center;gap:4px;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .progress-bar-container[_ngcontent-%COMP%] .progress-bar-meta[_ngcontent-%COMP%] .progress-timer[_ngcontent-%COMP%] .timer-icon[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-tip-banner[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;padding:8px 12px;background:#2563eb0d;border-left:3px solid #1a73e8;border-radius:0 6px 6px 0;font-size:12px;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-tip-banner[_ngcontent-%COMP%] .tip-icon[_ngcontent-%COMP%]{font-size:16px;color:#1a73e8;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%]{display:flex;flex-direction:column;border:1px solid #1e293b;border-radius:8px;background:#0f172a;overflow:hidden}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#1e293b;color:#94a3b8;font-size:12px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .logs-header-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .logs-header-left[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .btn-copy-logs[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;background:#334155;border:none;border-radius:4px;color:#f8fafc;font-size:11px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .btn-copy-logs[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .btn-copy-logs[_ngcontent-%COMP%]:hover{background:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-body[_ngcontent-%COMP%]{margin:0;padding:12px;max-height:180px;overflow-y:auto;font-family:JetBrains Mono,monospace,sans-serif;font-size:11px;line-height:1.5;color:#cbd5e1;white-space:pre-wrap;word-break:break-all}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-body[_ngcontent-%COMP%] .log-line[_ngcontent-%COMP%]{margin-bottom:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-boot-tracker-card[_ngcontent-%COMP%] .boot-logs-drawer[_ngcontent-%COMP%] .logs-body[_ngcontent-%COMP%] .log-empty[_ngcontent-%COMP%]{color:#64748b;font-style:italic}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:14px;padding:18px 20px;background:#fff8f8;border:1.5px solid rgba(239,68,68,.3);border-radius:12px;box-shadow:0 4px 14px #ef44440f}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%] .failed-icon-box[_ngcontent-%COMP%]{width:34px;height:34px;border-radius:8px;background:#ef44441f;color:#dc2626;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%] .failed-icon-box[_ngcontent-%COMP%] .failed-icon[_ngcontent-%COMP%]{font-size:20px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%] .failed-titles[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%] .failed-titles[_ngcontent-%COMP%] .failed-title[_ngcontent-%COMP%]{margin:0;font-size:15px;font-weight:700;color:#dc2626}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .failed-header-left[_ngcontent-%COMP%] .failed-titles[_ngcontent-%COMP%] .failed-desc[_ngcontent-%COMP%]{margin:0;font-size:13px;color:#0f172a;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .btn-dismiss-failed[_ngcontent-%COMP%]{background:transparent;border:none;color:#64748b;cursor:pointer;padding:4px;border-radius:4px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-header[_ngcontent-%COMP%] .btn-dismiss-failed[_ngcontent-%COMP%]:hover{background:#0000000d;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-logs-box[_ngcontent-%COMP%]{display:flex;flex-direction:column;background:#1e1e2e;border-radius:8px;overflow:hidden}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-logs-box[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background:#282a36;color:#f55;font-size:11px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-logs-box[_ngcontent-%COMP%] .logs-header[_ngcontent-%COMP%] .btn-copy-logs[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;background:#44475a;border:none;border-radius:4px;color:#f8fafc;font-size:10px;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-logs-box[_ngcontent-%COMP%] .failed-logs-pre[_ngcontent-%COMP%]{margin:0;padding:10px 12px;max-height:120px;overflow-y:auto;font-family:JetBrains Mono,monospace;font-size:11px;line-height:1.4;color:#f8f8f2;white-space:pre-wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;padding:6px 16px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%]{border-radius:6px;font-size:12px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-retry-launch[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.88);box-shadow:0 2px 8px -1px #0f172a0d,inset 0 1px 1px #fffffff2;color:#475569;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]:hover:not(:disabled){background:#ffffffeb;border-color:#bfdbfef2;color:#1a73e8;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done){color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%]{border-radius:6px;font-size:12px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-restart-adb-sub[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-dismiss-flat[_ngcontent-%COMP%]{background:transparent;border:none;color:#475569;font-size:12px;font-weight:600;cursor:pointer;padding:6px 10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .emulator-failed-card[_ngcontent-%COMP%] .failed-actions-bar[_ngcontent-%COMP%] .btn-dismiss-flat[_ngcontent-%COMP%]:hover{text-decoration:underline}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}@media(max-width:1100px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%]{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:620px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%]{grid-template-columns:1fr}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;cursor:pointer;text-align:left;transition:all .18s ease;outline:none;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-icon-box[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:8px;background:#fff;border:1px solid #e2e8f0;color:#475569;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-icon-box[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:17px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-info[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:1px;min-width:0;flex:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-info[_ngcontent-%COMP%] .method-title-row[_ngcontent-%COMP%]{display:flex;align-items:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-info[_ngcontent-%COMP%] .method-title-row[_ngcontent-%COMP%] .method-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-left[_ngcontent-%COMP%] .method-info[_ngcontent-%COMP%] .method-desc[_ngcontent-%COMP%]{font-size:11.5px;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-right[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-left:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-right[_ngcontent-%COMP%] .radio-indicator[_ngcontent-%COMP%]{width:16px;height:16px;border-radius:50%;border:1.5px solid #cbd5e1;background:#fff;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%] .method-card-right[_ngcontent-%COMP%] .radio-indicator.checked[_ngcontent-%COMP%]{border-color:#1a73e8;background:#1a73e8;box-shadow:inset 0 0 0 3px #fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card.active[_ngcontent-%COMP%]{background:#fff;border-color:#1a73e8;box-shadow:0 0 0 1px #1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card.active[_ngcontent-%COMP%] .method-icon-box[_ngcontent-%COMP%]{background:#eff6ff;color:#1a73e8;border-color:#bfdbfe}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card.active[_ngcontent-%COMP%] .method-title[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .connection-methods-nav[_ngcontent-%COMP%] .method-nav-card[_ngcontent-%COMP%]:hover:not(.active){background:#fff;border-color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;background:#fffbeb;border:1px solid #fde68a;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .active-remote-copy[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;min-width:0;color:#78350f;font-size:11.5px;line-height:1.45}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .active-remote-copy[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{color:#d97706;font-size:17px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .active-remote-copy[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:32px;padding:0 12px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.88);box-shadow:0 2px 8px -1px #0f172a0d,inset 0 1px 1px #fffffff2;color:#475569;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]:hover:not(:disabled){background:#ffffffeb;border-color:#bfdbfef2;color:#1a73e8;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]:hover:not(:disabled) .material-symbols-outlined[_ngcontent-%COMP%]:not(.feedback-done){color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]:disabled{opacity:.55;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}@media(max-width:700px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%]{align-items:stretch;flex-direction:column}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .active-remote-notice[_ngcontent-%COMP%] .btn-use-local-adb[_ngcontent-%COMP%]{width:100%}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%]{background:#fff;border:1px solid #e2e8f0;border-radius:10px;padding:14px 16px;display:flex;flex-direction:column;gap:12px;animation:_ngcontent-%COMP%_fadeIn .15s ease-out}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .section-header-bar[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:6px;padding-bottom:6px;border-bottom:1px solid #f1f5f9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .section-header-bar[_ngcontent-%COMP%] .section-title-group[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .section-header-bar[_ngcontent-%COMP%] .section-title-group[_ngcontent-%COMP%] .header-icon[_ngcontent-%COMP%]{font-size:17px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .section-header-bar[_ngcontent-%COMP%] .section-title-group[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .section-header-bar[_ngcontent-%COMP%] .section-title-group[_ngcontent-%COMP%] .count-badge[_ngcontent-%COMP%]{font-size:10.5px;font-weight:600;padding:1.5px 6px;border-radius:6px;background:#f1f5f9;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;gap:12px;min-width:0;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%]:hover{border-color:#cbd5e1;background:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-avatar[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:6px;background:#fff;border:1px solid #e2e8f0;color:#1a73e8;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-avatar[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:17px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-meta[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:1px;flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-meta[_ngcontent-%COMP%] .avd-heading[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-meta[_ngcontent-%COMP%] .avd-heading[_ngcontent-%COMP%] .avd-name[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-main[_ngcontent-%COMP%] .avd-meta[_ngcontent-%COMP%] .avd-heading[_ngcontent-%COMP%] .avd-type-pill[_ngcontent-%COMP%]{font-size:9.5px;font-weight:600;padding:1px 5px;border-radius:4px;background:#fff;border:1px solid #e2e8f0;color:#64748b;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:0 15px;height:30px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .avds-section[_ngcontent-%COMP%] .avds-list[_ngcontent-%COMP%] .avd-card-item[_ngcontent-%COMP%] .avd-item-actions[_ngcontent-%COMP%] .btn-avd-launch[_ngcontent-%COMP%] .spinner-icon[_ngcontent-%COMP%]{width:13px;height:13px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:_ngcontent-%COMP%_spin .8s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:12px;padding:12px 14px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%] .empty-avd-icon-box[_ngcontent-%COMP%]{width:34px;height:34px;border-radius:6px;background:#fff;border:1px solid #e2e8f0;color:#64748b;display:flex;align-items:center;justify-content:center;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%] .empty-avd-icon-box[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:19px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%] .banner-titles[_ngcontent-%COMP%] .banner-title-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%] .banner-titles[_ngcontent-%COMP%] .banner-title-row[_ngcontent-%COMP%] .empty-title[_ngcontent-%COMP%]{margin:0;font-size:13px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .banner-left[_ngcontent-%COMP%] .banner-titles[_ngcontent-%COMP%] .empty-desc[_ngcontent-%COMP%]{margin:2px 0 0;font-size:12px;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .mode-switch-pills[_ngcontent-%COMP%]{display:inline-flex;background:#fff;border:1px solid #e2e8f0;padding:2px;border-radius:6px;gap:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .mode-switch-pills[_ngcontent-%COMP%] .pill-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:4px;border:none;background:transparent;color:#475569;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .mode-switch-pills[_ngcontent-%COMP%] .pill-btn[_ngcontent-%COMP%] .pill-icon[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .mode-switch-pills[_ngcontent-%COMP%] .pill-btn[_ngcontent-%COMP%]:hover{color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .empty-avd-banner[_ngcontent-%COMP%] .mode-switch-pills[_ngcontent-%COMP%] .pill-btn.active[_ngcontent-%COMP%]{background:#1a73e8;color:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .emu-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .guide-link[_ngcontent-%COMP%]{color:#1a73e8;font-weight:600;text-decoration:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .emu-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .guide-link[_ngcontent-%COMP%]:hover{text-decoration:underline}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%] .step-badge-num[_ngcontent-%COMP%]{width:22px;height:22px;border-radius:50%;background:#eff6ff;color:#1a73e8;font-size:11.5px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%] .step-content[_ngcontent-%COMP%]{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%] .step-content[_ngcontent-%COMP%] .step-title[_ngcontent-%COMP%]{font-size:12.5px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%] .step-content[_ngcontent-%COMP%] .step-text[_ngcontent-%COMP%]{margin:0;font-size:11.5px;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .emulator-setup-guide-container[_ngcontent-%COMP%] .cli-steps-list[_ngcontent-%COMP%] .cli-step-item[_ngcontent-%COMP%] .step-content[_ngcontent-%COMP%] .small-box[_ngcontent-%COMP%]{margin-top:4px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .usb-guide-intro[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-guide-intro[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .remote-guide-intro[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:600;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .usb-guide-intro[_ngcontent-%COMP%] .intro-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-guide-intro[_ngcontent-%COMP%] .intro-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .remote-guide-intro[_ngcontent-%COMP%] .intro-icon[_ngcontent-%COMP%]{font-size:17px;color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}@media(max-width:900px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%]{grid-template-columns:repeat(2,1fr)}}@media(max-width:520px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%]{grid-template-columns:1fr}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:6px;min-width:0;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%]:hover{border-color:#cbd5e1;background:#fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .step-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .step-card-header[_ngcontent-%COMP%] .step-number-badge[_ngcontent-%COMP%]{width:20px;height:20px;border-radius:4px;background:#1a73e8;color:#fff!important;font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .step-card-header[_ngcontent-%COMP%] .step-card-icon[_ngcontent-%COMP%]{font-size:17px;color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .step-card-title[_ngcontent-%COMP%]{font-size:12.5px;font-weight:600;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .steps-flow-grid[_ngcontent-%COMP%] .flow-step-card[_ngcontent-%COMP%] .step-card-desc[_ngcontent-%COMP%]{font-size:11.5px;color:#64748b;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:12px 14px;display:flex;flex-direction:column;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%]{display:flex;align-items:flex-end;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:3px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col.ip-col[_ngcontent-%COMP%]{flex:1 1 180px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col.port-col[_ngcontent-%COMP%]{flex:0 1 90px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-label[_ngcontent-%COMP%]{font-size:11px;font-weight:600;color:#64748b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-input-wrap[_ngcontent-%COMP%]{display:flex;align-items:center;background:#fff;border:1px solid #e2e8f0;border-radius:6px;padding:0 8px;height:34px;transition:border-color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-input-wrap[_ngcontent-%COMP%]:focus-within{border-color:#1a73e8;box-shadow:0 0 0 2px #1a73e81a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-input-wrap[_ngcontent-%COMP%] .wrap-icon[_ngcontent-%COMP%]{font-size:15px;color:#94a3b8;margin-right:5px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-input-wrap[_ngcontent-%COMP%] .wifi-input[_ngcontent-%COMP%]{flex:1;min-width:0;border:none;outline:none;background:transparent;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:12px;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-input-col[_ngcontent-%COMP%] .wifi-input-wrap[_ngcontent-%COMP%] .wifi-input[_ngcontent-%COMP%]::placeholder{color:#94a3b8;font-family:Google Sans Text,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:11.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%]{flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:0 16px;height:34px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-form-row[_ngcontent-%COMP%] .wifi-btn-col[_ngcontent-%COMP%] .btn-wifi-connect[_ngcontent-%COMP%] .spinning[_ngcontent-%COMP%]{animation:_ngcontent-%COMP%_spin 1s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-status-alert[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;padding:6px 10px;border-radius:6px;font-size:11.5px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-status-alert[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-status-alert.success[_ngcontent-%COMP%]{background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .wifi-interactive-box[_ngcontent-%COMP%] .wifi-status-alert.error[_ngcontent-%COMP%]{background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .info-callout-box[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:8px;padding:8px 12px;background:#eff6ff;border:1px solid #bfdbfe;border-radius:6px;font-size:11.5px;color:#1557b0;line-height:1.45}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel[_ngcontent-%COMP%] .info-callout-box[_ngcontent-%COMP%] .callout-icon[_ngcontent-%COMP%]{font-size:16px;color:#1a73e8;flex-shrink:0;margin-top:1px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px;padding:12px 14px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%]{display:flex;align-items:flex-end;gap:8px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:3px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col.host-col[_ngcontent-%COMP%]{flex:1 1 220px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col.port-col[_ngcontent-%COMP%]{flex:0 1 120px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-label[_ngcontent-%COMP%]{color:#64748b;font-size:11px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-input-wrap[_ngcontent-%COMP%]{display:flex;align-items:center;height:36px;padding:0 9px;background:#fff;border:1px solid #e2e8f0;border-radius:6px;transition:border-color .15s ease,box-shadow .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-input-wrap[_ngcontent-%COMP%]:focus-within{border-color:#1a73e8;box-shadow:0 0 0 2px #1a73e81a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-input-wrap[_ngcontent-%COMP%] .wrap-icon[_ngcontent-%COMP%]{margin-right:6px;color:#94a3b8;font-size:15px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-input-wrap[_ngcontent-%COMP%] .remote-adb-input[_ngcontent-%COMP%]{flex:1;min-width:0;border:0;outline:0;background:transparent;color:#0f172a;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%] .remote-adb-input-wrap[_ngcontent-%COMP%] .remote-adb-input[_ngcontent-%COMP%]::placeholder{color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%]{flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:5px;height:36px;padding:0 16px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}@media(max-width:640px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%]{align-items:stretch;flex-direction:column}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col.host-col[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-input-col.port-col[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%]{flex:1 1 auto;width:100%}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-form-row[_ngcontent-%COMP%] .remote-adb-btn-col[_ngcontent-%COMP%] .btn-remote-adb-connect[_ngcontent-%COMP%]{width:100%}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remember-remote-adb[_ngcontent-%COMP%]{display:inline-flex;align-items:center;align-self:flex-start;gap:7px;color:#475569;font-size:11.5px;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remember-remote-adb[_ngcontent-%COMP%] input[_ngcontent-%COMP%]{width:15px;height:15px;margin:0;accent-color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;align-self:flex-end;gap:5px;min-width:150px;height:36px;padding:0 16px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%]{border-radius:6px;font-family:inherit;font-size:12px;font-weight:600;cursor:pointer}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .btn-use-tested-endpoint[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-status-alert[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:6px;padding:7px 10px;border-radius:6px;font-size:11.5px;font-weight:600;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-status-alert[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-status-alert.success[_ngcontent-%COMP%]{color:#047857;background:#ecfdf5;border:1px solid #a7f3d0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-adb-status-alert.error[_ngcontent-%COMP%]{color:#b91c1c;background:#fef2f2;border:1px solid #fecaca}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%]{display:flex;flex-direction:column;overflow:hidden;background:#fff;border:1px solid #e2e8f0;border-radius:7px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:9px;min-width:0;padding:8px 10px;border-bottom:1px solid #f1f5f9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%]:last-child{border-bottom:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] > .material-symbols-outlined[_ngcontent-%COMP%]{color:#1a73e8;font-size:17px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] .remote-device-copy[_ngcontent-%COMP%]{display:flex;flex:1;flex-direction:column;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] .remote-device-copy[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:#0f172a;font-size:12px;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] .remote-device-copy[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{overflow:hidden;color:#64748b;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:10.5px;text-overflow:ellipsis;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] .remote-device-state[_ngcontent-%COMP%]{color:#b45309;font-size:10.5px;font-weight:600;text-transform:capitalize}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-interactive-box[_ngcontent-%COMP%] .remote-device-results[_ngcontent-%COMP%] .remote-device-row[_ngcontent-%COMP%] .remote-device-state.ready[_ngcontent-%COMP%]{color:#047857}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .foolproof-guide-container[_ngcontent-%COMP%] .method-details-panel.remote-adb-panel[_ngcontent-%COMP%] .remote-adb-explanation[_ngcontent-%COMP%]{margin:0;color:#64748b;font-size:11.5px;line-height:1.5}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px;padding:12px 16px;background:#fffbeb;border:1px solid #fde68a;border-radius:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .alert-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .alert-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .alert-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .alert-icon[_ngcontent-%COMP%]{font-size:24px;color:#d97706;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%]{flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-title[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#b45309;margin-bottom:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .unauth-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .unauth-desc[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .locked-details[_ngcontent-%COMP%] .locked-desc[_ngcontent-%COMP%]{font-size:11.5px;color:#78350f;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:0 12px;height:30px;background:#fef3c7bf;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(253,230,138,.85);border-radius:6px;box-shadow:0 1px 3px #d9770614,inset 0 1px 1px #fffc;font-family:inherit;font-size:11.5px;font-weight:600;color:#b45309;cursor:pointer;flex-shrink:0;transition:all .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:hover:not(:disabled), .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:hover:not(:disabled){background:#fde68af2;border-color:#d97706;transform:translateY(-.5px);box-shadow:0 3px 8px #d9770626}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:active, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:active{transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .unauthorized-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .unauthorized-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-retry-auth[_ngcontent-%COMP%]:disabled, .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .device-locked-panel[_ngcontent-%COMP%] .device-locked-card[_ngcontent-%COMP%] .btn-check-unlock[_ngcontent-%COMP%]:disabled{cursor:not-allowed;opacity:.65}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .error-msg-box[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;padding:10px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;font-size:12px;color:#b91c1c}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .error-msg-box[_ngcontent-%COMP%] .err-icon[_ngcontent-%COMP%]{font-size:17px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .install-commands-row[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .install-commands-row[_ngcontent-%COMP%] .install-card[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;padding:10px 12px;display:flex;flex-direction:column;gap:6px;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .install-commands-row[_ngcontent-%COMP%] .install-card.highlight-os[_ngcontent-%COMP%]{background:#eff6ff;border-color:#bfdbfe}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .install-commands-row[_ngcontent-%COMP%] .install-card[_ngcontent-%COMP%] .install-os[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;font-size:11.5px;font-weight:600;color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .adb-missing-panel[_ngcontent-%COMP%] .install-commands-row[_ngcontent-%COMP%] .install-card[_ngcontent-%COMP%] .install-os[_ngcontent-%COMP%] .detected-badge[_ngcontent-%COMP%]{font-size:9.5px;font-weight:700;color:#1a73e8;background:#dbeafe;padding:1px 5px;border-radius:4px;text-transform:uppercase}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 20px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;box-shadow:0 1px 3px #00000008,0 4px 12px #00000005;transition:all .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%]:hover{border-color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:14px;min-width:0;flex:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .step-badge[_ngcontent-%COMP%]{width:32px;height:32px;border-radius:8px;display:inline-flex;align-items:center;justify-content:center;font-size:13.5px;font-weight:700;background:#f1f5f9;color:#475569;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .step-badge[_ngcontent-%COMP%] .step-check-icon[_ngcontent-%COMP%]{font-size:18px;font-weight:700;color:inherit}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .step-badge.step-pass[_ngcontent-%COMP%]{background:#ecfdf5;color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .ready-text-group[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .ready-text-group[_ngcontent-%COMP%] .ready-title[_ngcontent-%COMP%]{margin:0;font-size:15px;font-weight:600;color:#0f172a;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .ready-banner-left[_ngcontent-%COMP%] .ready-text-group[_ngcontent-%COMP%] .ready-desc[_ngcontent-%COMP%]{margin:0;font-size:12.5px;color:#475569;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;padding:0 18px;height:36px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]{border-radius:8px;font-family:inherit;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%] .rocket-icon[_ngcontent-%COMP%]{font-size:16px;transition:transform .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]:hover:not(:disabled) .rocket-icon[_ngcontent-%COMP%]{transform:translate(2px) translateY(-2px)}@media(max-width:768px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%]{flex-direction:column;align-items:stretch}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .setup-guide-view[_ngcontent-%COMP%] .ready-banner-footer[_ngcontent-%COMP%] .btn-proceed-launch[_ngcontent-%COMP%]{justify-content:center}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:space-between;gap:8px;padding:0 6px 0 10px;height:32px;background:#0f172a;color:#f8fafc;border-radius:6px;font-family:JetBrains Mono,Fira Code,Roboto Mono,SFMono-Regular,Consolas,monospace;font-size:11.5px;min-width:0;max-width:100%}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .terminal-prefix[_ngcontent-%COMP%]{color:#64748b;-webkit-user-select:none;user-select:none;flex-shrink:0;font-weight:700;font-size:11px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .code-text[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{flex:1;min-width:0;color:#38bdf8!important;font-family:inherit;background:transparent!important;border:none!important;padding:0!important;font-size:inherit;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:3px;padding:0 8px;height:22px;background:#ffffff24;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);color:#fff!important;border:1px solid rgba(255,255,255,.2);border-radius:5px;box-shadow:inset 0 1px 1px #fff3;font-family:Google Sans Text,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:10.5px;font-weight:600;cursor:pointer;transition:all .18s ease;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%]:hover{background:#ffffff42;border-color:#ffffff59;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%]:active{transform:scale(.96)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code.copied[_ngcontent-%COMP%]{background:#10b98159;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border-color:#10b98199;color:#34d399!important;box-shadow:0 0 10px #10b9814d,inset 0 1px 1px #fff6}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code.copied[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box[_ngcontent-%COMP%] .btn-copy-code.copied[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:#34d399!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box.small-box[_ngcontent-%COMP%]{height:30px;font-size:11.5px;padding:0 6px 0 10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box.small-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%]{height:22px;padding:0 8px;font-size:10.5px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .code-box.small-box[_ngcontent-%COMP%] .btn-copy-code[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px;animation:_ngcontent-%COMP%_fadeIn .22s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .launcher-warning-bar[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;padding:10px 16px;background:#fffbeb;border:1px solid #fde68a;border-radius:12px;color:#92400e;font-size:13px;cursor:pointer;transition:all .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .launcher-warning-bar[_ngcontent-%COMP%] .warn-icon[_ngcontent-%COMP%]{font-size:18px;color:#d97706;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .launcher-warning-bar[_ngcontent-%COMP%] .arrow[_ngcontent-%COMP%]{font-size:16px;margin-left:auto}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .launcher-warning-bar[_ngcontent-%COMP%]:hover{background:#fef3c7;border-color:#fcd34d}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%]{background:#ffffffe0;backdrop-filter:blur(16px) saturate(190%);-webkit-backdrop-filter:blur(16px) saturate(190%);border:1px solid rgba(255,255,255,.95);box-shadow:0 20px 50px -12px #0f172a14,0 1px 3px #00000005,inset 0 1px 1px #fffffff2;border-radius:20px;padding:18px 20px 14px;transition:border-color .3s cubic-bezier(.4,0,.2,1),box-shadow .3s cubic-bezier(.4,0,.2,1),background .3s cubic-bezier(.4,0,.2,1);position:relative}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card.is-pro-active[_ngcontent-%COMP%]{border-color:#ddd6fef2;box-shadow:0 24px 60px -12px #6366f129,0 1px 3px #00000005,inset 0 1px 1px #fffffff2}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 12px;background:linear-gradient(135deg,#eef2fff2,#f5f3fff2);border:1px solid rgba(199,210,254,.85);border-radius:10px;margin-bottom:12px;animation:_ngcontent-%COMP%_slideUpFade .22s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .suggestion-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .suggestion-left[_ngcontent-%COMP%] .sparkle-icon[_ngcontent-%COMP%]{font-size:17px;color:#6366f1;flex-shrink:0;animation:_ngcontent-%COMP%_sparklePulse 2.5s ease-in-out infinite alternate}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .suggestion-left[_ngcontent-%COMP%] .suggestion-text[_ngcontent-%COMP%]{font-size:12.5px;color:#3730a3;line-height:1.4}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .suggestion-left[_ngcontent-%COMP%] .suggestion-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:#1e1b4b;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .btn-switch-pro[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:6px;border:none;background:#6366f1;color:#fff;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;flex-shrink:0;transition:all .18s cubic-bezier(.4,0,.2,1);box-shadow:0 1px 3px #6366f140}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .btn-switch-pro[_ngcontent-%COMP%] .btn-arrow[_ngcontent-%COMP%]{font-size:14px;transition:transform .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .btn-switch-pro[_ngcontent-%COMP%]:hover{background:#4f46e5;box-shadow:0 2px 6px #6366f159;transform:translateY(-.5px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .btn-switch-pro[_ngcontent-%COMP%]:hover .btn-arrow[_ngcontent-%COMP%]{transform:translate(2px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .intent-suggestion-strip[_ngcontent-%COMP%] .btn-switch-pro[_ngcontent-%COMP%]:active{transform:scale(.96)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-input-area[_ngcontent-%COMP%]{width:100%}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-input-area[_ngcontent-%COMP%] .composer-textarea[_ngcontent-%COMP%]{width:100%;border:none;outline:none;background:transparent;padding:0;margin:0;font-family:inherit;font-size:15.5px;line-height:1.6;color:#0f172a;resize:vertical;min-height:90px;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-input-area[_ngcontent-%COMP%] .composer-textarea[_ngcontent-%COMP%]::placeholder{color:#94a3b8;font-weight:400}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-input-area[_ngcontent-%COMP%] .composer-textarea[_ngcontent-%COMP%]:disabled{color:#94a3b8;cursor:not-allowed}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%]{display:flex;align-items:center;gap:9px;padding:8px 12px;border-radius:9px;background:#f8fafcbf;border:1px solid rgba(241,245,249,.95);margin-top:10px;margin-bottom:12px;transition:all .25s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-icon-wrap[_ngcontent-%COMP%]{width:22px;height:22px;border-radius:6px;display:inline-flex;align-items:center;justify-content:center;background:#1a73e81a;color:#1a73e8;flex-shrink:0;transition:all .25s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-icon-wrap[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%]{flex:1;min-width:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%]{margin:0;font-size:12.5px;line-height:1.45;color:#475569;letter-spacing:-.005em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] .mode-name[_ngcontent-%COMP%]{font-weight:700;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] .bullet[_ngcontent-%COMP%]{color:#94a3b8;margin:0 3px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:#0f172a;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] .limitation-tag[_ngcontent-%COMP%]{color:#64748b;font-size:11.5px;margin-left:3px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor.is-pro[_ngcontent-%COMP%]{background:linear-gradient(135deg,#f5f3ffcc,#faf5ffcc);border-color:#ede9fef2}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor.is-pro[_ngcontent-%COMP%] .descriptor-icon-wrap[_ngcontent-%COMP%]{background:#7c3aed1f;color:#7c3aed}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor.is-pro[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%]{color:#334155}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor.is-pro[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] .mode-name[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-model-descriptor.is-pro[_ngcontent-%COMP%] .descriptor-body[_ngcontent-%COMP%] .desc-line[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:#1e1b4b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .error-banner[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;padding:8px 12px;background:#fef2f2;border:1px solid #fecaca;border-radius:8px;color:#b91c1c;font-size:12px;margin-bottom:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .error-banner[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%]{font-size:16px;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding-top:10px;border-top:1px solid rgba(241,245,249,.95);gap:12px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%]{display:flex;align-items:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%]{display:inline-flex;align-items:center;background:#f1f5f9d9;padding:3px;border-radius:18px;gap:3px;border:1px solid rgba(226,232,240,.88);width:172px;box-sizing:border-box}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%]{flex:1 1 0;width:50%;height:32px;padding:0 10px;border-radius:15px;border:none;background:transparent;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;transition:all .2s cubic-bezier(.4,0,.2,1);outline:none;box-sizing:border-box;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%] .btn-icon[_ngcontent-%COMP%]{font-size:16px;color:#64748b;flex-shrink:0;transition:color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{font-size:13px;font-weight:500;color:#475569;line-height:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%]:hover:not(.active){background:#ffffffb3}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%]:hover:not(.active) .btn-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%]:hover:not(.active) .btn-text[_ngcontent-%COMP%]{color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.active[_ngcontent-%COMP%]{background:#fff;box-shadow:0 1px 4px #00000014,0 1px 2px #0000000a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.active[_ngcontent-%COMP%] .btn-icon[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.active[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#0f172a;font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.btn-pro.active[_ngcontent-%COMP%]{background:linear-gradient(135deg,#6366f1,#7c3aed);box-shadow:0 2px 8px #6366f159}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.btn-pro.active[_ngcontent-%COMP%] .btn-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn.btn-pro.active[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-left[_ngcontent-%COMP%] .model-toggle-capsule[_ngcontent-%COMP%] .model-toggle-btn[_ngcontent-%COMP%]:active{transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .char-count[_ngcontent-%COMP%]{font-size:11.5px;color:#94a3b8;font-variant-numeric:tabular-nums}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:38px;padding:0 20px;background:linear-gradient(135deg,#1a73e8eb,#2563ebd9);backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(255,255,255,.28);box-shadow:0 4px 14px #1a73e852,inset 0 1px 1px #ffffff73;color:#fff!important;transition:background .2s cubic-bezier(.4,0,.2,1),border-color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),opacity .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] span[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#fff!important}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#1a73e8,#2563ebf2);box-shadow:0 6px 20px #1a73e86b,inset 0 1px 1px #fff9;transform:translateY(-1px)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.97) translateY(0);box-shadow:0 2px 8px #1a73e859}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]:disabled{background:#94a3b880;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);border:1px solid rgba(255,255,255,.2);box-shadow:none;opacity:.65;cursor:not-allowed;transform:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]{border-radius:12px;font-family:inherit;font-size:13.5px;font-weight:700;cursor:pointer;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer.btn-run-pro[_ngcontent-%COMP%]{background:linear-gradient(135deg,#6366f1eb,#7c3aedd9);box-shadow:0 4px 14px #6366f152,inset 0 1px 1px #ffffff73}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer.btn-run-pro[_ngcontent-%COMP%]:hover:not(:disabled){background:linear-gradient(135deg,#6366f1,#7c3aedf2);box-shadow:0 6px 20px #6366f16b,inset 0 1px 1px #fff9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] .run-icon[_ngcontent-%COMP%]{font-size:18px;line-height:1;transition:transform .2s cubic-bezier(.34,1.56,.64,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%]:hover:not(:disabled) .run-icon[_ngcontent-%COMP%]{transform:scale(1.15) rotate(15deg)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .composer-card[_ngcontent-%COMP%] .composer-action-bar[_ngcontent-%COMP%] .bar-right[_ngcontent-%COMP%] .btn-run-composer[_ngcontent-%COMP%] .spinner[_ngcontent-%COMP%]{width:14px;height:14px;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:_ngcontent-%COMP%_spin .8s linear infinite}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%]{background:#ffffffe0;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(226,232,240,.95);border-radius:16px;padding:12px 18px 14px;box-shadow:0 1px 3px #00000005,0 8px 24px -6px #0f172a0a;display:flex;flex-direction:column;gap:8px;animation:_ngcontent-%COMP%_slideUpFade .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-card-header[_ngcontent-%COMP%]{display:flex;align-items:baseline;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-card-header[_ngcontent-%COMP%] .outputter-title[_ngcontent-%COMP%]{font-size:13px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-card-header[_ngcontent-%COMP%] .outputter-optional-tag[_ngcontent-%COMP%]{font-size:11.5px;font-weight:500;color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%]{display:flex;align-items:center;background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:0 12px;height:38px;transition:all .18s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%]:focus-within{background:#fff;border-color:#7c3aed;box-shadow:0 0 0 3px #7c3aed1a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%] .outputter-standalone-input[_ngcontent-%COMP%]{flex:1;border:none;outline:none;background:transparent;font-family:inherit;font-size:13px;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%] .outputter-standalone-input[_ngcontent-%COMP%]::placeholder{color:#94a3b8;font-weight:400}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%] .btn-clear-standalone[_ngcontent-%COMP%]{border:none;background:transparent;color:#94a3b8;cursor:pointer;padding:3px;border-radius:4px;display:flex;align-items:center;justify-content:center;transition:color .15s ease,background .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%] .btn-clear-standalone[_ngcontent-%COMP%]:hover{color:#475569;background:#0000000d}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-outputter-standalone-card[_ngcontent-%COMP%] .outputter-input-wrapper[_ngcontent-%COMP%] .btn-clear-standalone[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%]{--%NS%tip-w: 320px;--%NS%thumb-w: 22px;--%NS%thumb-h: 14px;--%NS%col-gap: 28px;--%NS%accent: #6366f1;position:relative;background:#ffffffe0;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(226,232,240,.95);border-radius:16px;padding:12px 18px 14px;box-shadow:0 1px 3px #00000005,0 8px 24px -6px #0f172a0a;display:flex;flex-direction:column;gap:10px;animation:_ngcontent-%COMP%_slideUpFade .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card.tip-open[_ngcontent-%COMP%]{z-index:30}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-card-header[_ngcontent-%COMP%]{display:flex;align-items:baseline;justify-content:space-between;gap:10px;min-height:16px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-card-header[_ngcontent-%COMP%] .tuning-title[_ngcontent-%COMP%]{font-size:13px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-card-header[_ngcontent-%COMP%] .btn-tuning-reset[_ngcontent-%COMP%]{border:none;background:transparent;padding:0;font-family:inherit;font-size:12px;font-weight:500;color:#94a3b8;cursor:pointer;transition:color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-card-header[_ngcontent-%COMP%] .btn-tuning-reset[_ngcontent-%COMP%]:hover{color:#475569}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-card-header[_ngcontent-%COMP%] .btn-tuning-reset[_ngcontent-%COMP%]:focus-visible{outline:2px solid var(--%NS%accent);outline-offset:3px;border-radius:3px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-sliders[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px var(--%NS%col-gap)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px;min-width:0;--%NS%tip-min: 0px;--%NS%tip-max: calc(100% - var(--%NS%tip-w))}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider.verify[_ngcontent-%COMP%]{--%NS%tip-max: calc(200% + var(--%NS%col-gap) - var(--%NS%tip-w))}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider.explore[_ngcontent-%COMP%]{--%NS%accent: #0ea5e9;--%NS%tip-min: calc(-100% - var(--%NS%col-gap))}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-head[_ngcontent-%COMP%]{display:flex;align-items:baseline;gap:8px;font-size:13px;line-height:18px;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-head[_ngcontent-%COMP%] .slider-name[_ngcontent-%COMP%]{font-weight:400;color:#334155}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-head[_ngcontent-%COMP%] .slider-value[_ngcontent-%COMP%]{font-weight:600}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-ends[_ngcontent-%COMP%]{display:flex;justify-content:space-between;font-size:11.5px;line-height:14px;color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-track-wrap[_ngcontent-%COMP%]{position:relative;height:26px;display:flex;align-items:center}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-rail[_ngcontent-%COMP%]{position:relative;width:100%;height:6px;border-radius:3px;background:#e2e8f0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-fill[_ngcontent-%COMP%]{position:absolute;top:0;bottom:0;left:0;width:calc(var(--%NS%pct) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2);border-radius:3px;background:color-mix(in srgb,var(--%NS%accent) 24%,#e2e8f0);transition:width .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-notch[_ngcontent-%COMP%]{position:absolute;top:50%;left:calc(var(--%NS%n) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2);width:4px;height:4px;border-radius:50%;background:#94a3b8;transform:translate(-50%,-50%);transition:background .15s ease;z-index:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-notch.reached[_ngcontent-%COMP%]{background:#fffffff2}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-thumb[_ngcontent-%COMP%]{position:absolute;top:50%;left:calc(var(--%NS%pct) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2);width:var(--%NS%thumb-w);height:var(--%NS%thumb-h);border-radius:5px;background:#fff;border:1px solid rgba(15,23,42,.1);box-shadow:0 1px 2px #0f172a1f,0 2px 6px #0f172a14;transform:translate(-50%,-50%);pointer-events:none;transition:left .15s ease,box-shadow .15s ease,border-color .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-solid[_ngcontent-%COMP%]{position:absolute;inset:0;border-radius:3px;background:var(--%NS%accent);transform-origin:right center;transform:scaleX(1);animation:_ngcontent-%COMP%_solidSweep 1s cubic-bezier(.4,0,.2,1) .2s both;pointer-events:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-dither[_ngcontent-%COMP%]{position:absolute;inset:0;display:block;overflow:hidden;border-radius:3px;pointer-events:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-dither[_ngcontent-%COMP%] rect[_ngcontent-%COMP%]{fill:var(--%NS%accent);animation:_ngcontent-%COMP%_ditherIn .3s ease-out both}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .thumb-ripple[_ngcontent-%COMP%]{position:absolute;top:50%;left:calc(var(--%NS%pct) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2);width:var(--%NS%thumb-w);height:var(--%NS%thumb-w);border-radius:50%;border:1.5px solid var(--%NS%accent);transform:translate(-50%,-50%) scale(.6);opacity:0;pointer-events:none;animation:_ngcontent-%COMP%_thumbRipple .6s ease-out forwards}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider.dragging[_ngcontent-%COMP%] .slider-thumb[_ngcontent-%COMP%]{border-color:color-mix(in srgb,var(--%NS%accent) 45%,transparent);box-shadow:0 1px 2px #0f172a1f,0 2px 8px #0f172a1a,0 0 0 4px color-mix(in srgb,var(--%NS%accent) 14%,transparent)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-input[_ngcontent-%COMP%]{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer;-webkit-appearance:none;appearance:none;background:transparent}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-input[_ngcontent-%COMP%]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:var(--%NS%thumb-w);height:26px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-input[_ngcontent-%COMP%]::-moz-range-thumb{width:var(--%NS%thumb-w);height:26px;border:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .slider-input[_ngcontent-%COMP%]:focus-visible ~ .slider-thumb[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%]:has(.slider-input:focus-visible) .slider-thumb[_ngcontent-%COMP%]{box-shadow:0 1px 2px #0f172a1f,0 0 0 2px #fff,0 0 0 4px var(--%NS%accent)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip-arrow[_ngcontent-%COMP%]{position:absolute;bottom:calc(100% + 2px);left:calc(var(--%NS%pct) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2);width:10px;height:10px;background:#fff;border-right:1px solid rgb(226,232,240);border-bottom:1px solid rgb(226,232,240);transform:translate(-50%,50%) rotate(45deg);z-index:41;pointer-events:none}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%]{position:absolute;bottom:calc(100% + 7px);left:clamp(var(--%NS%tip-min),var(--%NS%pct) * (100% - var(--%NS%thumb-w)) + var(--%NS%thumb-w) / 2 - var(--%NS%tip-w) / 2,var(--%NS%tip-max));width:var(--%NS%tip-w);max-width:calc(100vw - 32px);padding:12px 14px;background:#fff;border:1px solid rgb(226,232,240);border-radius:12px;box-shadow:0 12px 32px -8px #0f172a2e,0 2px 6px #0f172a0f;z-index:40;pointer-events:none;animation:_ngcontent-%COMP%_tipPop .16s ease-out;font-size:12px;line-height:1.5;color:#334155;display:flex;flex-direction:column;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-heading[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:1px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-heading[_ngcontent-%COMP%] .tip-title[_ngcontent-%COMP%]{font-size:13.5px;font-weight:600;color:#0f172a;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-heading[_ngcontent-%COMP%] .tip-latency[_ngcontent-%COMP%]{font-size:11.5px;color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-tagline[_ngcontent-%COMP%]{margin:0;color:#0f172a}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-section[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-section[_ngcontent-%COMP%] ul[_ngcontent-%COMP%]{margin:0;padding-left:16px;display:flex;flex-direction:column;gap:2px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-section[_ngcontent-%COMP%] li[_ngcontent-%COMP%]::marker{color:#cbd5e1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-section.skips[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{color:#64748b}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-section-title[_ngcontent-%COMP%]{font-size:10.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider[_ngcontent-%COMP%] .tuning-tip[_ngcontent-%COMP%] .tip-best[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px;color:#475569}@media(max-width:760px){.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-sliders[_ngcontent-%COMP%]{grid-template-columns:minmax(0,1fr);gap:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider.verify[_ngcontent-%COMP%], .home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .pro-tuning-card[_ngcontent-%COMP%] .tuning-slider.explore[_ngcontent-%COMP%]{--%NS%tip-min: 0px;--%NS%tip-max: calc(100% - var(--%NS%tip-w))}}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:12px;margin-top:10px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .section-label[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;font-family:inherit;font-size:13px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .section-label[_ngcontent-%COMP%] .section-icon[_ngcontent-%COMP%]{font-size:17px;color:#6366f1;animation:_ngcontent-%COMP%_sparklePulse 3s ease-in-out infinite alternate}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .section-label[_ngcontent-%COMP%] .device-context-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:12px;background:#10b9811a;border:1px solid rgba(16,185,129,.25);color:#059669;font-size:11px;font-weight:600;line-height:1.3}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .section-label[_ngcontent-%COMP%] .device-context-pill[_ngcontent-%COMP%] .dev-icon[_ngcontent-%COMP%]{font-size:13px;color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:3px;background:#ffffffa6;backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);padding:3px;border-radius:16px;border:1px solid rgba(226,232,240,.85);box-shadow:0 1px 3px #00000005}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:12px;border:none;background:transparent;font-family:inherit;font-size:11.5px;font-weight:600;color:#64748b;cursor:pointer;transition:all .18s ease;white-space:nowrap}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn[_ngcontent-%COMP%] .tab-mini-icon[_ngcontent-%COMP%]{font-size:13px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn[_ngcontent-%COMP%]:hover:not(.active){color:#0f172a;background:#fff9}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn.active[_ngcontent-%COMP%]{color:#0f172a;background:#fff;box-shadow:0 2px 6px #0f172a14,inset 0 1px #fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn.active.cat-device[_ngcontent-%COMP%]{color:#059669}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn.active.cat-flash[_ngcontent-%COMP%]{color:#1a73e8}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .category-tabs-group[_ngcontent-%COMP%] .cat-tab-btn.active.cat-pro[_ngcontent-%COMP%]{color:#7c3aed}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-shuffle-suggestions[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:5px 12px;border-radius:14px;background:#ffffffb8;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.85);color:#64748b;font-family:inherit;font-size:11.5px;font-weight:600;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-shuffle-suggestions[_ngcontent-%COMP%] .shuffle-icon[_ngcontent-%COMP%]{font-size:14px;transition:transform .2s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-shuffle-suggestions[_ngcontent-%COMP%]:hover{background:#fff;color:#0f172a;border-color:#cbd5e1e6;transform:translateY(-1px);box-shadow:0 2px 8px #0f172a0f}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-shuffle-suggestions[_ngcontent-%COMP%]:hover .shuffle-icon[_ngcontent-%COMP%]{transform:rotate(45deg)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-header-bar[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-shuffle-suggestions[_ngcontent-%COMP%]:active{transform:scale(.97)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(auto-fill,minmax(285px,1fr));gap:12px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%]{display:flex;flex-direction:column;justify-content:space-between;padding:14px 16px;border-radius:16px;background:#ffffffb8;backdrop-filter:blur(10px) saturate(180%);-webkit-backdrop-filter:blur(10px) saturate(180%);border:1px solid rgba(226,232,240,.85);box-shadow:0 2px 8px -1px #0f172a0a,inset 0 1px 1px #fffffff2;cursor:pointer;transition:background .22s cubic-bezier(.4,0,.2,1),transform .22s cubic-bezier(.4,0,.2,1),box-shadow .22s cubic-bezier(.4,0,.2,1),border-color .22s cubic-bezier(.4,0,.2,1);-webkit-user-select:none;user-select:none;position:relative;overflow:hidden;min-height:128px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%]:hover{background:#fffffff2;transform:translateY(-2px);box-shadow:0 8px 24px -4px #0f172a14,0 2px 6px #00000005,inset 0 1px 1px #fff;border-color:#bfdbfef2}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card.is-pro-card[_ngcontent-%COMP%]:hover{border-color:#c7d2fef2;box-shadow:0 8px 24px -4px #6366f11f,inset 0 1px 1px #fff}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%]:active{transform:scale(.985)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:8px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .app-icons-cluster[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .app-icons-cluster[_ngcontent-%COMP%] .app-icon-badge[_ngcontent-%COMP%]{width:26px;height:26px;border-radius:8px;background:#f1f5f9;display:flex;align-items:center;justify-content:center;color:#475569;border:1px solid rgba(226,232,240,.9);flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .app-icons-cluster[_ngcontent-%COMP%] .app-icon-badge[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{font-size:14px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .app-icons-cluster[_ngcontent-%COMP%] .app-names-label[_ngcontent-%COMP%]{font-size:11.5px;font-weight:600;color:#475569;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .mode-badge[_ngcontent-%COMP%]{padding:2px 7px;border-radius:8px;font-size:10.5px;font-weight:700;line-height:1.2;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .mode-badge.mode-flash[_ngcontent-%COMP%]{background:#1a73e81a;color:#1a73e8;border:1px solid rgba(26,115,232,.2)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-top-row[_ngcontent-%COMP%] .mode-badge.mode-pro[_ngcontent-%COMP%]{background:#6366f11f;color:#6366f1;border:1px solid rgba(99,102,241,.25)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%]{margin:8px 0 6px}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .card-title[_ngcontent-%COMP%]{font-size:13.5px;font-weight:600;color:#0f172a;margin:0 0 3px;line-height:1.3;letter-spacing:-.01em}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .card-desc[_ngcontent-%COMP%]{font-size:12px;color:#64748b;margin:0;line-height:1.45;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:6px;padding-top:8px;border-top:1px solid rgba(241,245,249,.85)}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .tag-pill[_ngcontent-%COMP%]{font-size:11px;font-weight:600;color:#64748b;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .use-prompt-action[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:3px;font-size:11.5px;font-weight:600;color:#1a73e8;opacity:.85;transition:all .15s ease;flex-shrink:0}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .use-prompt-action[_ngcontent-%COMP%] .action-arrow[_ngcontent-%COMP%]{font-size:13px;transition:transform .15s ease}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%]:hover .use-prompt-action[_ngcontent-%COMP%]{opacity:1}.home-container[_ngcontent-%COMP%] .home-content[_ngcontent-%COMP%] .task-launcher-view[_ngcontent-%COMP%] .smart-suggestions-container[_ngcontent-%COMP%] .suggestions-cards-grid[_ngcontent-%COMP%] .suggestion-card[_ngcontent-%COMP%]:hover .use-prompt-action[_ngcontent-%COMP%] .action-arrow[_ngcontent-%COMP%]{transform:translate(2px)}@keyframes _ngcontent-%COMP%_spin{to{transform:rotate(360deg)}}@keyframes _ngcontent-%COMP%_fadeIn{0%{opacity:0;transform:translateY(3px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_slideUpFade{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_slideDownFade{0%{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_floatGlow1{0%{transform:translate(0) scale(1)}50%{transform:translate(35px,45px) scale(1.06)}to{transform:translate(-25px,25px) scale(.96)}}@keyframes _ngcontent-%COMP%_floatGlow2{0%{transform:translate(0) scale(1)}50%{transform:translate(-40px,40px) scale(1.08)}to{transform:translate(20px,60px) scale(.94)}}@keyframes _ngcontent-%COMP%_floatGlow3{0%{transform:translate(0) scale(1)}50%{transform:translate(25px,-30px) scale(1.04)}to{transform:translate(-30px,15px) scale(.97)}}@keyframes _ngcontent-%COMP%_readyPulseGlow{0%{transform:scale(.97)}50%{transform:scale(1.06)}to{transform:scale(.98)}}@keyframes _ngcontent-%COMP%_statusDotPulse{0%{transform:scale(.95);box-shadow:0 0 #10b981b3}70%{transform:scale(1);box-shadow:0 0 0 5px #10b98100}to{transform:scale(.95);box-shadow:0 0 #10b98100}}@keyframes _ngcontent-%COMP%_sparklePulse{0%{transform:scale(.92) rotate(0);opacity:.8}50%{transform:scale(1.12) rotate(8deg);opacity:1}to{transform:scale(.92) rotate(0);opacity:.8}}@media(prefers-reduced-motion:reduce){.home-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow[_ngcontent-%COMP%], .sparkle-icon[_ngcontent-%COMP%], .status-dot[_ngcontent-%COMP%], .slider-dither[_ngcontent-%COMP%] rect[_ngcontent-%COMP%], .slider-solid[_ngcontent-%COMP%], .thumb-ripple[_ngcontent-%COMP%]{animation:none!important}}@keyframes _ngcontent-%COMP%_ditherIn{0%{opacity:0}}@keyframes _ngcontent-%COMP%_solidSweep{0%{transform:scaleX(0)}to{transform:scaleX(1)}}@keyframes _ngcontent-%COMP%_thumbRipple{0%{opacity:.7;transform:translate(-50%,-50%) scale(.6)}to{opacity:0;transform:translate(-50%,-50%) scale(2.6)}}@keyframes _ngcontent-%COMP%_tipPop{0%{opacity:0;transform:translateY(6px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}'],changeDetection:1})};function $n(n){return n?n.split(/(\*\*.*?\*\*|`.*?`)/g).filter(e=>e!=="").map(e=>e.startsWith("**")&&e.endsWith("**")?{text:e.slice(2,-2),bold:!0,code:!1}:e.startsWith("`")&&e.endsWith("`")?{text:e.slice(1,-1),bold:!1,code:!0}:{text:e,bold:!1,code:!1}):[]}function Ss(n){return n?n.split(` +`).map(t=>{let e=t.trim(),o=t.length-t.trimStart().length;if(e.startsWith("- [x] ")||e.startsWith("- [X] ")){let a=e.substring(6);return{type:"checked",segments:$n(a),indent:o}}if(e.startsWith("- [/] ")){let a=e.substring(6);return{type:"progress",segments:$n(a),indent:o}}if(e.startsWith("- [ ] ")){let a=e.substring(6);return{type:"unchecked",segments:$n(a),indent:o}}let i=e.match(/^[-*]\s*(verify|assert)(@end)?\s*:\s*(.*)$/i);if(i){let a=i[1].toLowerCase(),s=!!i[2],c=i[3].trim();return{type:"verify",checkKind:a,atEnd:s,segments:$n(c),indent:o}}let r=e.match(/^[-*]\s*finding\s*:\s*(.*)$/i);if(r){let a=r[1].trim();return{type:"finding",checkKind:"finding",segments:$n(a),indent:o}}if(e.startsWith("- ")||e.startsWith("* ")){let a=e.substring(2);return{type:"list-item",segments:$n(a),indent:o}}if(e.startsWith("# ")){let a=e.substring(2);return{type:"h1",segments:$n(a),indent:o}}if(e.startsWith("## ")){let a=e.substring(3);return{type:"h2",segments:$n(a),indent:o}}if(e.startsWith("### ")){let a=e.substring(4);return{type:"h3",segments:$n(a),indent:o}}return e?{type:"text",segments:$n(t),indent:o}:{type:"empty",segments:[],indent:0}}):[]}function ks(n){let t=Ss(n),e=[],o=[],i=null,r=null,a=0;for(let s of t){if(s.type==="empty")continue;if((s.type==="h1"||s.type==="h2")&&!i){i=s.segments.map(p=>p.text).join("");continue}["checked","progress","unchecked"].includes(s.type)&&(!r||s.indent<=(r._indent??0))?(a++,r={index:a,type:s.type,segments:s.segments,subSteps:[],checks:[],_indent:s.indent},e.push(r)):r&&s.indent>(r._indent??0)?s.type==="verify"||s.type==="assert"||s.type==="finding"?r.checks.push(s):r.subSteps.push(s):o.push(s)}return{title:i,milestones:e,otherLines:o}}function WP(n){if(!n)return null;let t=n.trim();if(t.includes("```json")){let e=t.split("```json");e.length>1&&(t=e[1].split("```")[0].trim())}else if(t.includes("```")){let e=t.split("```");e.length>1&&(t=e[1].split("```")[0].trim())}try{let e=JSON.parse(t);if(e&&typeof e=="object"&&"success"in e)return{success:!!e.success,reason:e.reason||"No reason provided."}}catch(e){}return null}function Vi(n){return n?n.replace(/`([^`]+)`/g,'$1').replace(/\*\*\*([^*]+)\*\*\*/g,"$1").replace(/___([^_]+)___/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/__([^_]+)__/g,"$1").replace(/\*([^*]+)\*/g,"$1").replace(/\b_([^_]+)_\b/g,"$1").replace(/~~([^~]+)~~/g,"$1"):""}function Om(n){if(!n)return"";let o=n.replace(/<\/?(thought|short_term_memory|strong_term_memory|reasoning|plan|task_plan|call_tool)[^\n>]*>?/gi,"").replace(/\r\n/g,` +`).replace(/\r/g,` +`).replace(/\n{3,}/g,` + +`).trim().replace(/&/g,"&").replace(//g,">").split(` +`),i=[],r=null,a=!1,s="",c=[],p=()=>{r&&(i.push(``),r=null)};for(let f=0;f${A}`),a=!1,c=[],s=""}else p(),a=!0,s=C.substring(3).trim(),c=[];continue}if(a){c.push(_);continue}let h=C.match(/^(#{1,6})\s+(.*)$/);if(h){p();let A=h[1].length,N=Vi(h[2]);i.push(`${N}`);continue}if(/^(\-{3,}|\*{3,}|_{3,})$/.test(C)){p(),i.push('
');continue}if(C.startsWith("> ")||C.startsWith(">")){p();let A=Vi(C.replace(/^>\s?/,""));i.push(`
${A}
`);continue}let P=_.match(/^(\s*)([\*\-])\s+(.*)$/);if(P){r!=="ul"&&(p(),r="ul",i.push("
    "));let A=P[1].length,E=Math.max(0,Math.floor((A-2)/2))*12,U=E?` style="margin-left: ${E}px"`:"",ne=P[3],J=ne.match(/^(verify|assert)(@end)?\s*:\s*(.*)$/i),X=ne.match(/^finding\s*:\s*(.*)$/i);if(J){let W=J[1].toLowerCase(),Me=J[2]?"@end":"",Ee=W==="assert"?" assert":"",ft=Vi(J[3].trim());i.push(`${W}${Me}${ft}`)}else if(X){let W=Vi(X[1].trim());i.push(`finding${W}`)}else{let W=Vi(ne);i.push(`${W}`)}continue}let T=_.match(/^(\s*)(\d+)\.\s+(.*)$/);if(T){r!=="ol"&&(p(),r="ol",i.push("
      "));let A=T[1].length,E=Math.max(0,Math.floor((A-2)/2))*12,U=E?` style="margin-left: ${E}px"`:"",ne=Vi(T[3]);i.push(`${ne}`);continue}if(p(),C==="")i.push('
      ');else{let A=Vi(_);i.push(`
      ${A}
      `)}}if(a){let f=c.join(` +`);i.push(`
      ${f}
      `)}return p(),i.join(` +`)}function Gr(n,t=0){if(n!=null){if(typeof n=="number")return n<1e11?n*1e3:n;let e=new Date(n).getTime();if(!isNaN(e)&&e>0)return e}return t}function KP(n,t){if(!t)return-1;let e=String(t);return n.findIndex(o=>o.type==="checker"&&o.data?.trace_id&&String(o.data.trace_id)===e)}function zT(n){for(let t=n.length-1;t>=0;t--)if(n[t].type==="checker"&&n[t].data?.isCompleted===!1)return t;return-1}function BT(n,t){let e=t.data||{};if(!e.event)return;if(e.event==="run_outcome"){let s="checker-outcome",c=n.findIndex(_=>_.id===s),p=F(S(S({},c>-1?n[c].data:{}),e),{phase:"outcome",status:"done",isCompleted:!0,generic_tools:[]}),f={id:s,type:"checker",timestamp:t.timestamp,data:p};c>-1?n[c]=f:n.push(f);return}if(!e.attempt_id)return;let o=`checker-${e.attempt_id}`,i=n.findIndex(s=>s.id===o),r=i>-1?n[i]:null,a=typeof e.ts=="number"?e.ts:t.timestamp?new Date(t.timestamp).getTime()/1e3:void 0;if(e.event==="attempt_started"){let s=F(S(S({},r?.data||{}),e),{status:r?.data?.isCompleted?r.data.status:"running",isCompleted:r?.data?.isCompleted??!1,started_at:r?.data?.started_at??a,verdicts:r?.data?.verdicts||[],findings:r?.data?.findings||[],generic_tools:r?.data?.generic_tools||[]}),c={id:o,type:"checker",timestamp:r?.timestamp||t.timestamp,data:s};r?n[i]=c:n.push(c);return}if(e.event==="attempt_finished"){let s=r?.data?.started_at??a,c=Array.isArray(e.verdicts)?e.verdicts:r?.data?.verdicts||[],p=r?.data?.items?.length?r.data.items:c.map(C=>({kind:C.kind,text:C.item_text,when:C.when})),f=F(S(S({},r?.data||{}),e),{status:e.status||"done",verdicts:c,items:p,findings:Array.isArray(e.findings)?e.findings:r?.data?.findings||[],generic_tools:r?.data?.generic_tools||[],started_at:s,finished_at:a,duration:s!==void 0&&a!==void 0?Math.max(0,a-s):void 0,isCompleted:!0}),_={id:o,type:"checker",timestamp:r?.timestamp||t.timestamp,data:f};r?n[i]=_:n.push(_)}}function HT(n){let t=new Map;n.forEach((e,o)=>{e.type==="checker"&&e.data?.trace_id&&t.set(String(e.data.trace_id),o)}),t.size!==0&&n.forEach((e,o)=>{if(e.type==="checker")return;let i=e.data?.generic_tools||[];if(i.length===0)return;let r=[],a=!1;for(let s of i){let c=s?.trace_id?String(s.trace_id):"",p=s?.parent_trace_id?String(s.parent_trace_id):"";if(c&&t.has(c)&&s?.type==="agent"){a=!0;continue}let f=p?t.get(p):void 0;if(f===void 0){r.push(s);continue}let _=n[f],C=_.data.generic_tools||[];C.some(h=>h.trace_id&&h.trace_id===s.trace_id)||(n[f]=F(S({},_),{data:F(S({},_.data),{generic_tools:[...C,s]})})),a=!0}a&&(n[o]=F(S({},e),{data:F(S({},e.data),{generic_tools:r})}))})}function ZP(n){if(!n||n.length===0)return[];let t=[];return n.forEach(e=>{if(e){if(e.type==="checker_event"){BT(t,e);return}if(e.type==="llm_stream"){let o=e.data?.execution_id||e.id,i=e.data?.step_id,r=e.data?.stream_type||"text",a=e.data?.text||"",s=e.data?.isCompleted??!1,c=KP(t,e.data?.parent_trace_id);if(c>-1){let f=t[c],_=S({},f.data),C=Array.isArray(_.stream_segments)?[..._.stream_segments]:[],h=C.findIndex(A=>A.execution_id===o&&A.stream_type===r),P={execution_id:o,stream_type:r==="thinking"?"thinking":"text",text:a,timestamp:h>-1?C[h].timestamp:e.timestamp,isCompleted:s};h>-1?C[h]=P:C.push(P),_.stream_segments=C;let T=A=>C.filter(N=>N.stream_type===A&&N.text.trim()).map(N=>N.text).join(` + +`);_.operator_native_thinking=T("thinking"),_.operator_raw_thinking=T("text"),r==="thinking"?_.operator_native_thinking_timestamp=_.operator_native_thinking_timestamp||e.timestamp:_.operator_raw_thinking_timestamp=_.operator_raw_thinking_timestamp||e.timestamp,o&&!_.execution_id&&(_.execution_id=o),t[c]=F(S({},f),{data:_});return}let p=-1;if(i&&(p=t.findIndex(f=>f.id===`step-${i}`||f.data?.step_id===i)),p===-1&&o&&(p=t.findIndex(f=>f.data?.execution_id===o||f.id===`stream-${o}`)),p>-1){let f=t[p],_=S({},f.data);r==="thinking"?(_.operator_native_thinking=a,_.operator_native_thinking_timestamp=_.operator_native_thinking_timestamp||e.timestamp):(_.operator_raw_thinking=a,_.operator_raw_thinking_timestamp=_.operator_raw_thinking_timestamp||e.timestamp),i&&!_.step_id&&(_.step_id=i),o&&!_.execution_id&&(_.execution_id=o),_.isCompleted=s,t[p]=F(S({},f),{data:_})}else{let f=i?`step-${i}`:`stream-${o}`,_={execution_id:o,step_id:i,isCompleted:s,generic_tools:[]};r==="thinking"?(_.operator_native_thinking=a,_.operator_native_thinking_timestamp=e.timestamp):(_.operator_raw_thinking=a,_.operator_raw_thinking_timestamp=e.timestamp),t.push({id:f,type:i?"step":"llm_stream",timestamp:e.timestamp,data:_})}}else if(e.type==="step_recorded"||e.type==="step_updated"){let o=e.data.step_id||e.data.step_number||"unknown",i=t.findIndex(r=>r.id===`step-${o}`||r.data?.step_id===o);if(i>-1){let r=t[i],a=r.data.generic_tools||[],s=e.data.generic_tools||[],c=[...a];s.forEach(f=>{let _=c.findIndex(C=>C.trace_id&&f.trace_id&&C.trace_id===f.trace_id);_>-1?c[_]=S(S({},c[_]),f):c.push(f)});let p=F(S(S({},r.data),e.data),{operator_native_thinking:e.data.operator_native_thinking||r.data.operator_native_thinking||void 0,operator_raw_thinking:e.data.operator_raw_thinking||r.data.operator_raw_thinking||void 0,operator_native_thinking_timestamp:r.data.operator_native_thinking_timestamp||e.data.operator_native_thinking_timestamp||(e.data.operator_native_thinking?e.timestamp:void 0),operator_raw_thinking_timestamp:r.data.operator_raw_thinking_timestamp||e.data.operator_raw_thinking_timestamp||(e.data.operator_raw_thinking?e.timestamp:void 0),action_taken:e.data.action_taken||r.data.action_taken||void 0,last_execution_result:e.data.last_execution_result||r.data.last_execution_result||void 0,generic_tools:c,isCompleted:!0});t[i]=F(S({},r),{id:`step-${o}`,type:"step",timestamp:r.timestamp||e.timestamp,data:p})}else t.push({id:`step-${o}`,type:"step",timestamp:e.timestamp,data:F(S({},e.data),{isCompleted:!0,operator_native_thinking_timestamp:e.data.operator_native_thinking_timestamp||(e.data.operator_native_thinking?e.timestamp:void 0),operator_raw_thinking_timestamp:e.data.operator_raw_thinking_timestamp||(e.data.operator_raw_thinking?e.timestamp:void 0),generic_tools:e.data.generic_tools||[]})})}else if(e.type==="trace_recorded"){let o=e.data.type==="action",i=e.data.type==="tool"||o,r=e.data.type==="llm_call"&&(e.data.status==="failed"||e.data.status==="retrying");if(!i&&!r)return;let a=e.data.step_id,s=-1,c=KP(t,e.data.parent_trace_id);if(c===-1&&String(e.data.agent_name||"").toLowerCase()==="checker"&&(c=zT(t)),c>-1&&(s=c),s===-1&&e.data.trace_id&&(s=t.findIndex(p=>p.data?.generic_tools?.some(f=>f.trace_id===e.data.trace_id))),s===-1&&a&&(s=t.findIndex(p=>p.id===`step-${a}`||p.data?.step_id===a)),s===-1&&!a&&(s=t.findIndex(p=>p.id==="step-pre-planning"||p.data?.step_id==="pre-planning")),s===-1&&!a){let p=e.timestamp?new Date(e.timestamp).getTime():0,f=-1;for(let _=0;_=f&&(f=h,s=_)}}if(s>-1){let p=t[s].data,f=p.generic_tools?[...p.generic_tools]:[],_=e.data.trace_id?f.findIndex(C=>C.trace_id===e.data.trace_id):-1;_>-1?f[_]=S(S({},f[_]),e.data):f.push(e.data),t[s]=F(S({},t[s]),{data:F(S({},p),{generic_tools:f})})}else{let p=a||"pre-planning";t.push({id:`step-${p}`,type:"step",timestamp:e.timestamp,data:{step_id:p,isCompleted:!0,generic_tools:[e.data]}})}}}}),HT(t),t.sort((e,o)=>{let i=e.timestamp?new Date(e.timestamp).getTime():0,r=o.timestamp?new Date(o.timestamp).getTime():0;if(Math.abs(i-r)>50)return i-r;let a=p=>p.data?.step_number!==void 0&&p.data.step_number!==null?Number(p.data.step_number):p.data?.step_id==="pre-planning"||p.data?.step_type==="planning"?0:99999,s=a(e),c=a(o);return s!==c&&s!==99999&&c!==99999?s-c:i-r}),t}var YP=new WeakMap;function UT(n){if(typeof n=="string")return{isImage:n.includes("data:image/")||n.includes("base64,"),length:n.length};if(n===null||typeof n!="object")return{isImage:!1,length:String(n).length};let t=YP.get(n);if(t)return t;let e="";try{e=JSON.stringify(n)||""}catch(i){e=String(n)}let o={isImage:e.includes("data:image/")||e.includes("base64,"),length:e.length};return YP.set(n,o),o}function GT(n){if(!n||!n.data)return{total:0,prompt:0,completion:0};let t=n.data;if(t.total_tokens!==void 0&&t.total_tokens!==null&&Number(t.total_tokens)>0){let s=t.token_usage?.prompt_tokens||t.token_usage?.input_tokens||0,c=t.token_usage?.completion_tokens||t.token_usage?.output_tokens||0;return{total:Number(t.total_tokens),prompt:Number(s),completion:Number(c)}}if(t.token_usage&&typeof t.token_usage=="object"){let s=t.token_usage.prompt_tokens||t.token_usage.input_tokens||0,c=t.token_usage.completion_tokens||t.token_usage.output_tokens||0,p=t.token_usage.total_tokens||s+c;if(p>0)return{total:Number(p),prompt:Number(s),completion:Number(c)}}if(t.extra_metadata?.token_usage&&typeof t.extra_metadata.token_usage=="object"){let s=t.extra_metadata.token_usage,c=s.prompt_tokens||s.prompt_token_count||s.input_tokens||0,p=s.completion_tokens||s.candidates_token_count||s.output_tokens||0,f=s.total_tokens||s.total_token_count||c+p;if(f>0)return{total:Number(f),prompt:Number(c),completion:Number(p)}}let e=0,o=0,i=0;if(t.generic_tools&&Array.isArray(t.generic_tools)&&t.generic_tools.forEach(s=>{if(s&&s.payload){let c=s.payload.token_usage||s.payload.usage_metadata;if(c){let p=c.prompt_tokens||c.prompt_token_count||c.input_tokens||0,f=c.completion_tokens||c.candidates_token_count||c.output_tokens||0,_=c.total_tokens||c.total_token_count||p+f;e+=Number(p),o+=Number(f),i+=Number(_)}}}),i>0)return{total:i,prompt:e,completion:o};let r=0,a=0;if(t.operator_native_thinking&&(r+=t.operator_native_thinking.length),t.operator_raw_thinking&&(r+=t.operator_raw_thinking.length),t.generic_tools&&Array.isArray(t.generic_tools)&&t.generic_tools.forEach(s=>{if(s.payload){let c=UT(s.payload);c.isImage?a+=1:r+=c.length}}),r>20||a>0){let s=Math.round(r/4)+a*258;return{total:s,prompt:0,completion:s}}return{total:0,prompt:0,completion:0}}function QP(n){return!n||n<=0?"0 tokens":n<1e3?`${n} tokens`:n<1e6?`${(n/1e3).toFixed(1).replace(/\.0$/,"")}k tokens`:`${(n/1e6).toFixed(2).replace(/\.?0+$/,"")}M tokens`}function JP(n,t){if(!n||n.length===0)return[];let e=[],o=t?t*1e3:0;return n.forEach((i,r)=>{let a=i.timestamp?new Date(i.timestamp).getTime():Date.now();o===0&&a>0&&(o=a);let s=0;i.data?.duration&&typeof i.data.duration=="number"&&i.data.duration>0?s=Math.max(1,Math.round(i.data.duration)):a>=o&&o>0?s=Math.max(1,Math.round((a-o)/1e3)):s=1;let c=GT(i);e.push({id:`phase-${i.id||r}`,durationSeconds:s,tokens:c.total>0?c.total:void 0,promptTokens:c.prompt>0?c.prompt:void 0,completionTokens:c.completion>0?c.completion:void 0,blocks:[i]}),o=a}),e}function XP(n,t){if(!n||typeof n!="object")return[];let e=n.generic_tools?n.generic_tools.length:0,o=n.action_taken?n.action_taken.timestamp||n.action_taken.start_time||n.action_taken.created_at:null,i=typeof n.operator_native_thinking=="string"?n.operator_native_thinking.trim():"",r=typeof n.operator_raw_thinking=="string"?n.operator_raw_thinking.trim():"",a=Array.isArray(n.stream_segments)&&n.stream_segments.length>0?n.stream_segments:null,s=[e,o??"",n.operator_native_thinking_timestamp??"",n.operator_raw_thinking_timestamp??"",i.length,r.length,a?a.map(h=>`${h.execution_id}:${h.stream_type}:${h.text.length}`).join(","):""].join("|");if(t){let h=t.get(n);if(h?.signature===s)return h.events}let c=[],p=Gr(n.timestamp,0),f=0;if(a?a.forEach(h=>{!h.text||!h.text.trim()||c.push({type:h.stream_type==="thinking"?"thinking":"text",data:{text:h.text,execution_id:h.execution_id,segment:!0},timestamp:Gr(h.timestamp,p),sequence:f++})}):(i&&c.push({type:"thinking",data:{text:n.operator_native_thinking},timestamp:Gr(n.operator_native_thinking_timestamp,p),sequence:f++}),r&&c.push({type:"text",data:{text:n.operator_raw_thinking},timestamp:Gr(n.operator_raw_thinking_timestamp,p),sequence:f++})),n.action_taken&&(jn(n.action_taken)||jo(n.action_taken))){let h=Gr(n.action_taken.timestamp??n.action_taken.start_time??n.action_taken.created_at,p);c.push({type:"action",data:n.action_taken,timestamp:h,sequence:f++})}Vd(n.generic_tools).forEach(h=>{let P=Gr(h.timestamp??h.start_time??h.created_at,p);c.push({type:"tool",data:h,timestamp:P,sequence:f++})}),c.sort((h,P)=>h.timestamp-P.timestamp||h.sequence-P.sequence);let C=c.map(h=>({type:h.type,data:h.data,timestamp:h.timestamp}));return t&&t.set(n,{signature:s,events:C}),C}function eO(n){if(!n||n.length===0)return"No logs available.";for(let t=n.length-1;t>=0;t--){let e=n[t];if(e.type==="session_ended"&&(e.data?.status==="cancelled"||e.data?.was_stopped_manually))return"Task stopped manually.";if(e.type==="llm_stream"&&e.data?.text){let o=e.data.text;if(o.includes("```json")&&o.includes('"success"'))return"Execution completed and verified by checker."}if(e.type==="step_recorded"||e.type==="step_updated"){let o=e.data?.action_taken;if(o&&jo(o)){let i=$d(o);if(i)return i}if(e.data?.status==="completed")return e.data.message||"Task completed successfully."}}return"Execution session in progress or ended."}function tO(n,t){if(!n||!t||t.length===0)return 0;let e=n*1e3,o=t[t.length-1],i=o?.timestamp?new Date(o.timestamp).getTime():Date.now();return Math.max(0,Math.round((i-e)/1e3))}function nO(n,t,e=!0){if(!t||!e)return!1;if(!n||n.length===0)return!0;for(let i=n.length-1;i>=Math.max(0,n.length-3);i--){let r=n[i];if(r.type==="session_ended")return!1;if(r.type==="step_recorded"||r.type==="step_updated"){let a=r.data?.action_taken;if(a&&(a.action==="report_task_status"||a==="report_task_status"||jo(a))||r.data?.status==="completed"||r.data?.status==="failed")return!1}}return!n.some(i=>i&&i.type==="llm_stream"&&i.data&&i.data.isCompleted===!1)}var qT=n=>[n];function WT(n,t){n&1&&(l(0,"div",25),u(1," No active or queued tasks "),d())}function KT(n,t){n&1&&$(0,"span",33)}function YT(n,t){if(n&1&&(l(0,"span",35)(1,"span",44),u(2,"phone_android"),d(),l(3,"span",45),u(4),d()()),n&2){let e=m().$implicit,o=m(2);R("title","Device: "+o.getDeviceSerial(e)),g(4),O(o.getDeviceSerial(e))}}function ZT(n,t){n&1&&(l(0,"span",36),u(1,"\xB7"),d())}function QT(n,t){if(n&1){let e=L();l(0,"button",46),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.stopTask(r.session_id,i))}),l(1,"span",22),u(2,"stop_circle"),d()()}}function JT(n,t){if(n&1){let e=L();l(0,"button",47),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.deleteTask(r.session_id,i))}),l(1,"span",22),u(2,"delete"),d()()}}function XT(n,t){if(n&1){let e=L();l(0,"div",30),k("click",function(i){let r=v(e).$implicit,a=m(2);return y(a.selectTask(r.session_id,i))}),l(1,"div",31)(2,"span",32),M(3,KT,1,0,"span",33),u(4),it(5,"uppercase"),d(),l(6,"div",34),M(7,YT,5,2,"span",35),M(8,ZT,2,0,"span",36),l(9,"span",37),u(10),it(11,"date"),d(),M(12,QT,3,0,"button",38),M(13,JT,3,0,"button",39),d()(),l(14,"div",40),u(15),d(),l(16,"div",41)(17,"span",42),u(18),d(),l(19,"span",43),u(20),it(21,"date"),d()()()}if(n&2){let e=t.$implicit,o=m(2);w("selected",o.agentService.currentSessionId()===e.session_id)("running",o.getTaskStatus(e)==="running"),g(2),R("ngClass",o.getTaskStatus(e)),g(),b(o.getTaskStatus(e)==="running"?3:-1),g(),V(" ",wi(5,18,o.getTaskStatus(e))," "),g(3),b(o.getDeviceSerial(e)?7:-1),g(),b(o.getDeviceSerial(e)?8:-1),g(2),O(Et(11,20,e.start_time*1e3,"shortTime")),g(2),b(o.getTaskStatus(e)==="running"||o.getTaskStatus(e)==="paused"?12:-1),g(),b(o.getTaskStatus(e)!=="running"&&o.getTaskStatus(e)!=="paused"?13:-1),g(),R("title",xi(e.initial_goal)),g(),V(" ",e.initial_goal||"Unnamed Task"," "),g(2),R("title",e.session_id),g(),V("ID: ",e.session_id),g(2),O(Et(21,23,e.start_time*1e3,"MM/dd/yy"))}}function eA(n,t){n&1&&(l(0,"div",25),u(1," No past task history "),d())}function tA(n,t){n&1&&$(0,"span",33)}function nA(n,t){if(n&1&&(l(0,"span",35)(1,"span",44),u(2,"phone_android"),d(),l(3,"span",45),u(4),d()()),n&2){let e=m().$implicit,o=m(2);R("title","Device: "+o.getDeviceSerial(e)),g(4),O(o.getDeviceSerial(e))}}function oA(n,t){n&1&&(l(0,"span",36),u(1,"\xB7"),d())}function iA(n,t){if(n&1){let e=L();l(0,"button",47),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.deleteTask(r.session_id,i))}),l(1,"span",22),u(2,"delete"),d()()}}function rA(n,t){if(n&1){let e=L();l(0,"div",30),k("click",function(i){let r=v(e).$implicit,a=m(2);return y(a.selectTask(r.session_id,i))}),l(1,"div",31)(2,"span",32),M(3,tA,1,0,"span",33),u(4),it(5,"uppercase"),d(),l(6,"div",34),M(7,nA,5,2,"span",35),M(8,oA,2,0,"span",36),l(9,"span",37),u(10),it(11,"date"),d(),M(12,iA,3,0,"button",39),d()(),l(13,"div",40),u(14),d(),l(15,"div",41)(16,"span",42),u(17),d(),l(18,"span",43),u(19),it(20,"date"),d()()()}if(n&2){let e=t.$implicit,o=m(2);w("selected",o.agentService.currentSessionId()===e.session_id)("running",o.getTaskStatus(e)==="running"),g(2),R("ngClass",o.getTaskStatus(e)),g(),b(o.getTaskStatus(e)==="running"?3:-1),g(),V(" ",wi(5,17,o.getTaskStatus(e))," "),g(3),b(o.getDeviceSerial(e)?7:-1),g(),b(o.getDeviceSerial(e)?8:-1),g(2),O(Et(11,19,e.start_time*1e3,"shortTime")),g(2),b(o.getTaskStatus(e)!=="running"&&o.getTaskStatus(e)!=="paused"?12:-1),g(),R("title",xi(e.initial_goal)),g(),V(" ",e.initial_goal||"Unnamed Task"," "),g(2),R("title",e.session_id),g(),V("ID: ",e.session_id),g(2),O(Et(20,22,e.start_time*1e3,"MM/dd/yy"))}}function aA(n,t){if(n&1&&(l(0,"div",13)(1,"div",21)(2,"span",22),u(3,"list_alt"),d(),l(4,"span"),u(5,"Task Queue"),d()(),l(6,"div",23)(7,"span",24),u(8,"bolt"),d(),l(9,"span"),u(10),d()(),M(11,WT,2,0,"div",25),G(12,XT,22,26,"div",26,De().trackSession,!0),l(14,"div",27)(15,"span",28),u(16,"history"),d(),l(17,"span"),u(18),d()(),M(19,eA,2,0,"div",25),l(20,"div",29),G(21,rA,21,25,"div",26,De().trackSession,!0),d()()),n&2){let e=m();g(10),V("Active Queue (",e.activeQueue().length,")"),g(),b(e.activeQueue().length===0?11:-1),g(),q(e.activeQueue()),g(6),V("Past Runs & History (",e.historyTasks().length,")"),g(),b(e.historyTasks().length===0?19:-1),g(2),q(e.historyTasks())}}function sA(n,t){if(n&1){let e=L();l(0,"button",52),k("click",function(i){let r=v(e).$implicit,a=m(3);return y(a.selectNote(r,i))}),l(1,"span",53),u(2,"draft"),d(),u(3),d()}if(n&2){let e=t.$implicit,o=m(3);w("active",o.agentService.selectedNoteKey()===e),g(3),V(" ",e," ")}}function cA(n,t){if(n&1&&(l(0,"div",48)(1,"div",50),G(2,sA,4,3,"button",51,De().trackNoteKey,!0),d()()),n&2){let e=m(2);g(2),q(e.noteKeys())}}function lA(n,t){n&1&&(l(0,"div",25)(1,"span",54),u(2,"description"),d(),l(3,"span"),u(4,"No notes available for this session."),d()())}function dA(n,t){if(n&1&&(l(0,"h3",55),u(1),d()),n&2){let e=m(3);g(),O(e.parsedNote().title)}}function uA(n,t){n&1&&(l(0,"span",60),u(1,"check_box"),d())}function gA(n,t){n&1&&(l(0,"span",61),u(1,"indeterminate_check_box"),d())}function pA(n,t){n&1&&(l(0,"span",62),u(1,"check_box_outline_blank"),d())}function fA(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function mA(n,t){n&1&&(l(0,"span",69),u(1,"check_box"),d())}function _A(n,t){n&1&&(l(0,"span",70),u(1,"indeterminate_check_box"),d())}function hA(n,t){n&1&&(l(0,"span",71),u(1,"check_box_outline_blank"),d())}function CA(n,t){n&1&&(l(0,"span",72),u(1,"\u2022"),d())}function MA(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function bA(n,t){if(n&1&&(l(0,"span",73),G(1,MA,2,5,"span",64,Ie),d()),n&2){let e=m().$implicit;g(),q(e.segments)}}function PA(n,t){if(n&1&&(l(0,"div",68),M(1,mA,2,0,"span",69)(2,_A,2,0,"span",70)(3,hA,2,0,"span",71)(4,CA,2,0,"span",72),M(5,bA,3,0,"span",73),d()),n&2){let e,o=t.$implicit;Se("padding-left",o.indent>2?(o.indent-2)*12:0,"px"),R("ngClass",o.type),g(),b((e=o.type)==="checked"?1:e==="progress"?2:e==="unchecked"?3:e==="list-item"?4:-1),g(4),b(o.type!=="empty"?5:-1)}}function OA(n,t){if(n&1&&(l(0,"div",65),G(1,PA,6,5,"div",67,De().trackMarkdownLine,!0),d()),n&2){let e=m().$implicit;g(),q(e.subSteps)}}function vA(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function yA(n,t){if(n&1&&(l(0,"div",75)(1,"div",76)(2,"span",77),u(3),d()(),l(4,"div",78),G(5,vA,2,5,"span",64,Ie),d()()),n&2){let e=t.$implicit;Se("margin-left",e.indent>2?(e.indent-2)*12:0,"px"),R("ngClass",e.type),g(2),w("assert",e.checkKind==="assert"),g(),We("",e.checkKind||"verify","",e.atEnd?" @end":""),g(2),q(e.segments)}}function xA(n,t){if(n&1&&(l(0,"div",66),G(1,yA,7,7,"div",74,De().trackMarkdownLine,!0),d()),n&2){let e=m().$implicit;g(),q(e.checks)}}function wA(n,t){if(n&1&&(l(0,"div",58)(1,"div",59),M(2,uA,2,0,"span",60)(3,gA,2,0,"span",61)(4,pA,2,0,"span",62),l(5,"div",63),G(6,fA,2,5,"span",64,Ie),d()(),M(8,OA,3,0,"div",65),M(9,xA,3,0,"div",66),d()),n&2){let e,o=t.$implicit;R("ngClass",o.type),g(2),b((e=o.type)==="checked"?2:e==="progress"?3:e==="unchecked"?4:-1),g(4),q(o.segments),g(2),b(o.subSteps.length>0?8:-1),g(),b(o.checks.length>0?9:-1)}}function SA(n,t){if(n&1&&(l(0,"div",56),G(1,wA,10,4,"div",58,De().trackMilestone,!0),d()),n&2){let e=m(3);g(),q(e.parsedNote().milestones)}}function kA(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function EA(n,t){if(n&1&&(l(0,"div",81)(1,"div",76)(2,"span",77),u(3),d()(),l(4,"div",78),G(5,kA,2,5,"span",64,Ie),d()()),n&2){let e=m().$implicit;Se("margin-left",e.indent*12,"px"),R("ngClass",e.type),g(2),w("assert",e.checkKind==="assert"),g(),We("",e.checkKind||"verify","",e.atEnd?" @end":""),g(2),q(e.segments)}}function DA(n,t){n&1&&(l(0,"span",69),u(1,"check_box"),d())}function IA(n,t){n&1&&(l(0,"span",70),u(1,"indeterminate_check_box"),d())}function TA(n,t){n&1&&(l(0,"span",71),u(1,"check_box_outline_blank"),d())}function AA(n,t){n&1&&(l(0,"span",72),u(1,"\u2022"),d())}function NA(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function RA(n,t){if(n&1&&(l(0,"span",73),G(1,NA,2,5,"span",64,Ie),d()),n&2){let e=m(2).$implicit;g(),q(e.segments)}}function FA(n,t){if(n&1&&(l(0,"div",82),M(1,DA,2,0,"span",69)(2,IA,2,0,"span",70)(3,TA,2,0,"span",71)(4,AA,2,0,"span",72),M(5,RA,3,0,"span",73),d()),n&2){let e,o=m().$implicit;Se("margin-left",o.indent*12,"px"),R("ngClass",o.type),g(),b((e=o.type)==="checked"?1:e==="progress"?2:e==="unchecked"?3:e==="list-item"?4:-1),g(4),b(o.type!=="empty"?5:-1)}}function LA(n,t){if(n&1&&M(0,EA,7,7,"div",79)(1,FA,6,5,"div",80),n&2){let e=t.$implicit;b(e.type==="verify"||e.type==="finding"?0:1)}}function VA(n,t){if(n&1&&(l(0,"div",57),G(1,LA,2,1,null,null,De().trackMarkdownLine,!0),d()),n&2){let e=m(3);g(),q(e.parsedNote().otherLines)}}function jA(n,t){if(n&1&&(l(0,"div",49),M(1,dA,2,1,"h3",55),M(2,SA,3,0,"div",56),M(3,VA,3,0,"div",57),d()),n&2){let e=m(2);g(),b(e.parsedNote().title?1:-1),g(),b(e.parsedNote().milestones.length>0?2:-1),g(),b(e.parsedNote().otherLines.length>0?3:-1)}}function $A(n,t){if(n&1&&(l(0,"div",14)(1,"div",21)(2,"span",22),u(3,"description"),d(),l(4,"span"),u(5,"Notes & Plans"),d()(),M(6,cA,4,0,"div",48),M(7,lA,5,0,"div",25),M(8,jA,4,3,"div",49),d()),n&2){let e=m();g(6),b(e.noteKeys().length>1?6:-1),g(),b(e.noteKeys().length===0?7:-1),g(),b(e.noteKeys().length>0?8:-1)}}function zA(n,t){if(n&1&&(l(0,"div",83)(1,"span",93),u(2),d(),l(3,"span",94),u(4,"Architecture:"),d(),l(5,"span",95),u(6),d()()),n&2){let e=t,o=m(2);R("ngClass",o.getModelClass(e.name))("title",o.getArchitectureTooltip(e)),g(2),O(o.getModelIcon(e.name)),g(4),O(o.getModelDisplayName(e.name))}}function BA(n,t){n&1&&(l(0,"span",85),u(1," videocam "),d())}function HA(n,t){n&1&&(l(0,"span",86),u(1," progress_activity "),d())}function UA(n,t){n&1&&(l(0,"span",87),u(1," smart_display "),d())}function GA(n,t){n&1&&(l(0,"span",88),u(1," slideshow "),d())}function qA(n,t){n&1&&(l(0,"span",89),u(1," videocam_off "),d())}function WA(n,t){n&1&&(l(0,"span",85),u(1," videocam "),d())}function KA(n,t){n&1&&(l(0,"span",90),u(1,"Recording..."),d())}function YA(n,t){n&1&&(l(0,"span",90),u(1,"Preparing..."),d())}function ZA(n,t){n&1&&(l(0,"span",90),u(1,"Play Recording"),d())}function QA(n,t){n&1&&(l(0,"span",90),u(1,"Step Replay"),d())}function JA(n,t){n&1&&(l(0,"span",90),u(1,"Recording Failed"),d())}function XA(n,t){n&1&&(l(0,"span",90),u(1,"Screen Recording"),d())}function eN(n,t){n&1&&$(0,"span",91)}function tN(n,t){if(n&1&&(l(0,"span",92),u(1),d()),n&2){let e=m(2);g(),V(" ",e.agentService.currentSessionStepFrames().length," ")}}function nN(n,t){if(n&1){let e=L();l(0,"div",15),M(1,zA,7,4,"div",83),l(2,"button",84),k("click",function(){v(e);let i=m();return y(i.agentService.toggleVideoPlayer())}),M(3,BA,2,0,"span",85),M(4,HA,2,0,"span",86),M(5,UA,2,0,"span",87),M(6,GA,2,0,"span",88),M(7,qA,2,0,"span",89),M(8,WA,2,0,"span",85),M(9,KA,2,0,"span",90),M(10,YA,2,0,"span",90),M(11,ZA,2,0,"span",90),M(12,QA,2,0,"span",90),M(13,JA,2,0,"span",90),M(14,XA,2,0,"span",90),M(15,eN,1,0,"span",91),M(16,tN,2,1,"span",92),d()()}if(n&2){let e,o=m();g(),b((e=o.agentService.activeModel())?1:-1,e),g(),w("active",o.agentService.isVideoWindowOpen())("recording",o.agentService.isCurrentSessionRunning())("processing",!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()==="processing")("has-video",!o.agentService.isCurrentSessionRunning()&&!!o.agentService.currentSessionVideoUrl())("has-steps",!o.agentService.isCurrentSessionRunning()&&!o.agentService.currentSessionVideoUrl()&&o.agentService.hasCurrentSessionStepFrames())("failed",!o.agentService.isCurrentSessionRunning()&&!o.agentService.currentSessionVideoUrl()&&!o.agentService.hasCurrentSessionStepFrames()&&o.agentService.currentSessionRecordingStatus()==="failed"),R("title",o.getScreenRecordingButtonTitle()),g(),b(o.agentService.isCurrentSessionRunning()?3:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()==="processing"?4:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&o.agentService.currentSessionVideoUrl()?5:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&o.agentService.hasCurrentSessionStepFrames()?6:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&!o.agentService.hasCurrentSessionStepFrames()&&o.agentService.currentSessionRecordingStatus()==="failed"?7:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&!o.agentService.hasCurrentSessionStepFrames()&&o.agentService.currentSessionRecordingStatus()!=="failed"?8:-1),g(),b(o.agentService.isCurrentSessionRunning()?9:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()==="processing"?10:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&o.agentService.currentSessionVideoUrl()?11:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&o.agentService.hasCurrentSessionStepFrames()?12:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&!o.agentService.hasCurrentSessionStepFrames()&&o.agentService.currentSessionRecordingStatus()==="failed"?13:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&o.agentService.currentSessionRecordingStatus()!=="processing"&&!o.agentService.currentSessionVideoUrl()&&!o.agentService.hasCurrentSessionStepFrames()&&o.agentService.currentSessionRecordingStatus()!=="failed"?14:-1),g(),b(o.agentService.isCurrentSessionRunning()?15:-1),g(),b(!o.agentService.isCurrentSessionRunning()&&!o.agentService.currentSessionVideoUrl()&&o.agentService.hasCurrentSessionStepFrames()?16:-1)}}function oN(n,t){n&1&&(l(0,"div",17)(1,"span",96),u(2,"progress_activity"),d(),l(3,"span"),u(4,"Loading run history..."),d()())}function iN(n,t){n&1&&(l(0,"div",18)(1,"div",97)(2,"span",22),u(3,"sensors"),d()(),l(4,"h3"),u(5,"No Stream Activity Selected"),d(),l(6,"p"),u(7,"Please select a session from the Task List on the right to view its real-time log payloads."),d()())}function rN(n,t){if(n&1&&(l(0,"div",109),$(1,"span",110),l(2,"div",111)(3,"span",112),u(4),d()()()),n&2){let e=t.$implicit;g(),w("streaming",e.isActive),g(3),mr(" ",e.message," \xB7 ",e.isActive?"Waiting ":"Waited ","",e.elapsed," ")}}function aN(n,t){if(n&1&&(l(0,"div",98)(1,"div",102)(2,"span",103),u(3),d()(),l(4,"div",104)(5,"div",105)(6,"div",106)(7,"div",107)(8,"div",108),G(9,rN,5,5,"div",109,Ie),d()()()()()()),n&2){let e=m(2);g(3),V("Worked for ",e.startupPreparationDuration(),"s"),g(6),q(e.startupWorkItems())}}function sN(n,t){if(n&1&&(l(0,"span",116)(1,"span",117),u(2,"\xB7"),d(),l(3,"span",118),u(4),d()()),n&2){let e=m(2).$implicit,o=m(2);R("title",o.getTokenTooltip(e)),g(4),O(o.formatTokenCount(e.tokens))}}function cN(n,t){if(n&1&&(l(0,"div",115)(1,"span",103),u(2),d(),M(3,sN,5,2,"span",116),d()),n&2){let e=m(),o=e.$implicit,i=e.$index,r=m(2);w("first-phase",i===0&&r.startupWorkItems().length===0),g(2),We("",r.isCheckerPhase(o)?"Checked":"Worked"," for ",o.durationSeconds,"s"),g(),b(o.tokens?3:-1)}}function lN(n,t){if(n&1&&(l(0,"span",124),u(1),d()),n&2){let e=m(3).$implicit;R("title",e.data.subgoal_text),g(),O(e.data.subgoal_text)}}function dN(n,t){if(n&1&&(l(0,"li",127)(1,"span",128),u(2),d(),l(3,"span",129),u(4),d(),l(5,"span",130),u(6),d()()),n&2){let e=t.$implicit,o=t.$index,i=m(4).$implicit,r=m(4);g(2),O(o+1),g(2),O(e.text),g(2),O(r.getCheckMethodLabel(e,i))}}function uN(n,t){if(n&1&&(l(0,"ol",126),G(1,dN,7,3,"li",127,Do),d()),n&2){let e=m(3).$implicit,o=m(4);g(),q(o.getCheckerItems(e))}}function gN(n,t){if(n&1&&(l(0,"div",120)(1,"div",122),$(2,"span",110),l(3,"span",123),u(4),d(),M(5,lN,2,2,"span",124),l(6,"span",125),u(7),d()(),M(8,uN,3,0,"ol",126),d()),n&2){let e=m(2).$implicit,o=m(4);R("ngClass","tone-"+o.getCheckerTone(e)),g(2),w("streaming",o.isBlockRunning(e)),g(2),O(o.getCheckerPhaseLabel(e)),g(),b(e.data.phase!=="outcome"&&e.data.subgoal_text?5:-1),g(2),O(o.getCheckerStatusLabel(e)),g(),b(!o.isCheckerMuted(e)&&o.getCheckerItems(e).length>0?8:-1)}}function pN(n,t){if(n&1){let e=L();l(0,"div",135)(1,"div",136),k("click",function(){v(e);let i=m().$index,r=m(3).$implicit,a=m(4);return y(a.toggleCheckerSegment(r,i))}),$(2,"span",110),u(3," Thought "),d(),l(4,"div",137)(5,"div",138),$(6,"div",139),d()()()}if(n&2){let e=m(),o=e.$implicit,i=e.$index,r=m(3).$implicit,a=m(4);w("is-running",a.isCheckerSegmentRunning(r,i))("collapsed",a.isCheckerSegmentCollapsed(r,i)),g(2),w("streaming",a.isCheckerSegmentRunning(r,i)),g(4),R("innerHTML",a.renderMarkdown(o.data.text,r.id+"-seg-"+i),Pi)}}function fN(n,t){if(n&1&&(l(0,"div",140)(1,"div",141),$(2,"span",110),u(3," Work "),d(),l(4,"div",142),$(5,"div",139),d()()),n&2){let e=m(),o=e.$implicit,i=e.$index,r=m(3).$implicit,a=m(4);w("is-running",a.isCheckerSegmentRunning(r,i)),g(2),w("streaming",a.isCheckerSegmentRunning(r,i)),g(3),R("innerHTML",a.renderMarkdown(o.data.text,r.id+"-seg-"+i),Pi)}}function mN(n,t){if(n&1){let e=L();l(0,"div",135)(1,"div",136),k("click",function(){v(e);let i=m(4).$implicit,r=m(4);return y(r.toggleCollapse(i.data.execution_id||i.data.step_id||i.id))}),$(2,"span",110),u(3," Thought "),d(),l(4,"div",137)(5,"div",138),$(6,"div",139),d()()()}if(n&2){let e=m().$implicit,o=m(3).$implicit,i=m(4);w("is-running",i.isBlockRunning(o))("collapsed",i.collapsedStreams().has(o.data.execution_id||o.data.step_id||o.id)),g(2),w("streaming",i.isBlockRunning(o)),g(4),R("innerHTML",i.renderMarkdown(i.typedTextsSignal()[o.id+"-native"]||e.data.text,o.id+"-native"),Pi)}}function _N(n,t){if(n&1&&(l(0,"div",140)(1,"div",141),$(2,"span",110),u(3," Work "),d(),l(4,"div",142),$(5,"div",139),d()()),n&2){let e=m().$implicit,o=m(3).$implicit,i=m(4);w("is-running",i.isBlockRunning(o)),g(2),w("streaming",i.isBlockRunning(o)),g(3),R("innerHTML",i.renderMarkdown(i.typedTextsSignal()[o.id]||e.data.text,o.id),Pi)}}function hN(n,t){if(n&1&&(l(0,"div",150)(1,"div",152)(2,"span",153),u(3,"Summary:"),d(),l(4,"span",154),u(5),d()()()),n&2){let e=m(2).$implicit,o=m(7);g(5),V('"',o.getReportStatusExplanation(e.data),'"')}}function CN(n,t){if(n&1&&(l(0,"div",157)(1,"span",159),u(2,"Report & Findings"),d(),$(3,"span",160),d()),n&2){let e=m(3).$index,o=m(3).$implicit,i=m(4);g(3),R("innerHTML",i.renderMarkdown(t,"report-"+o.id+"-"+e),Pi)}}function MN(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Final Verified State"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(4).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPreImageUrl(a.data,r.data),r.data,i))}),l(4,"div",164),$(5,"img",165),d()()()}if(n&2){let e=m(4).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPreImageUrl(o.data,e.data),kt)}}function bN(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Post Verification"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(4).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPostImageUrl(a.data,r.data),null,i))}),l(4,"div",164),$(5,"img",166),d()()()}if(n&2){let e=m(4).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPostImageUrl(o.data,e.data),kt)}}function PN(n,t){if(n&1&&(l(0,"div",158),M(1,MN,6,1,"div",161),M(2,bN,6,1,"div",161),d()),n&2){let e=m(3).$implicit,o=m(3).$implicit,i=m(4);g(),b(i.getStepPreImageUrl(o.data,e.data)?1:-1),g(),b(i.getStepPostImageUrl(o.data,e.data)?2:-1)}}function ON(n,t){if(n&1&&(l(0,"div",155),k("click",function(o){return o.stopPropagation()}),l(1,"div",156),M(2,CN,4,1,"div",157),d(),M(3,PN,3,2,"div",158),d()),n&2){let e,o=m(2).$implicit,i=m(3).$implicit,r=m(4);g(2),b((e=r.getReportStatusExplanation(o.data))?2:-1,e),g(),b(r.getStepPreImageUrl(i.data,o.data)||r.getStepPostImageUrl(i.data,o.data)?3:-1)}}function vN(n,t){if(n&1){let e=L();l(0,"div",143)(1,"div",144),k("click",function(i){v(e);let r=m().$index,a=m(3).$implicit,s=m(4);return y(s.toggleActionExpanded("action-"+a.id+"-"+r,i))}),l(2,"span",145),u(3),d(),l(4,"span",146),u(5),d(),l(6,"span",147)(7,"span",148),u(8),d(),u(9),d(),l(10,"span",149),u(11),d()(),M(12,hN,6,1,"div",150),M(13,ON,4,2,"div",151),d()}if(n&2){let e=m(),o=e.$implicit,i=e.$index,r=m(3).$implicit,a=m(4);w("is-completed",a.getReportStatusValue(o.data)==="completed"||a.getReportStatusValue(o.data)==="success")("is-failed",a.getReportStatusValue(o.data)==="failed")("is-expanded",a.isActionExpanded("action-"+r.id+"-"+i)),g(2),w("is-failed",a.getReportStatusValue(o.data)==="failed"),g(),V(" ",a.getReportStatusValue(o.data)==="failed"?"cancel":"task_alt"," "),g(2),V(" ",a.getReportStatusValue(o.data)==="failed"?"Task Execution Failed":"Task Completed"," "),g(),w("is-failed",a.getReportStatusValue(o.data)==="failed")("is-completed",a.getReportStatusValue(o.data)!=="failed"),g(2),V(" ",a.getReportStatusValue(o.data)==="failed"?"error":"check_circle"," "),g(),V(" ",a.getReportStatusValue(o.data)==="failed"?"Failed":"Completed"," "),g(2),V(" ",a.isActionExpanded("action-"+r.id+"-"+i)?"expand_less":"expand_more"," "),g(),b(!a.isActionExpanded("action-"+r.id+"-"+i)&&a.getReportStatusExplanation(o.data)?12:-1),g(),b(a.isActionExpanded("action-"+r.id+"-"+i)?13:-1)}}function yN(n,t){n&1&&(l(0,"span",169)(1,"span",148),u(2,"error"),d(),u(3," Failed "),d())}function xN(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Target:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),V('"',o.getActionTargetText(e.data),'"')}}function wN(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(2),V("",o.getActionInputLabel(e.data),":"),g(2),V('"',o.getActionInputText(e.data),'"')}}function SN(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Coordinates:"),d(),l(3,"span",170),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),O(o.getActionCoords(e.data))}}function kN(n,t){if(n&1&&(l(0,"div",150),M(1,xN,5,1,"div",152),M(2,wN,5,2,"div",152),M(3,SN,5,1,"div",152),d()),n&2){let e=m(2).$implicit,o=m(7);g(),b(o.getActionTargetText(e.data)?1:-1),g(),b(o.getActionInputText(e.data)?2:-1),g(),b(o.getActionCoords(e.data)?3:-1)}}function EN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Target"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),V('"',o.getActionTargetText(e.data),'"')}}function DN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(2),O(o.getActionInputLabel(e.data)),g(2),V('"',o.getActionInputText(e.data),'"')}}function IN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Coordinates"),d(),l(3,"span",174),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),O(o.getActionCoords(e.data))}}function TN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Target Bounds"),d(),l(3,"span",174),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),V("[",o.getActionBounds(e.data),"]")}}function AN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Target Class"),d(),l(3,"span",174),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),O(o.getActionClass(e.data))}}function NN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2),d(),l(3,"span",174),u(4),d()()),n&2){let e=t.$implicit;g(2),O(e.key),g(2),O(e.value)}}function RN(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Action"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(3).$implicit,o=m(7);g(4),O(o.getActionTitle(e.data))}}function FN(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Pre-Action (Before)"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(4).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPreImageUrl(a.data,r.data),r.data,i))}),l(4,"div",164)(5,"img",175,0),k("load",function(){v(e);let i=Io(6),r=Io(8),a=m(4).$implicit,s=m(7);return y(s.onImageLoad(i,r,a.data))}),d(),$(7,"div",176,1),d()()()}if(n&2){let e=m(4).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPreImageUrl(o.data,e.data),kt)}}function LN(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Post-Action (After)"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(4).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPostImageUrl(a.data,r.data),null,i))}),l(4,"div",164),$(5,"img",177),d()()()}if(n&2){let e=m(4).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPostImageUrl(o.data,e.data),kt)}}function VN(n,t){if(n&1&&(l(0,"div",158),M(1,FN,9,1,"div",161),M(2,LN,6,1,"div",161),d()),n&2){let e=m(3).$implicit,o=m(3).$implicit,i=m(4);g(),b(i.getStepPreImageUrl(o.data,e.data)?1:-1),g(),b(i.getStepPostImageUrl(o.data,e.data)?2:-1)}}function jN(n,t){if(n&1&&(l(0,"div",172)(1,"span",178),u(2,"error"),d(),l(3,"span",179),u(4),d()()),n&2){let e=m(3).$implicit,o=m(3).$implicit,i=m(4);g(4),O(i.getActionErrorMessage(e.data,o.data))}}function $N(n,t){if(n&1&&(l(0,"div",155),k("click",function(o){return o.stopPropagation()}),l(1,"div",156),M(2,EN,5,1,"div",171),M(3,DN,5,2,"div",171),M(4,IN,5,1,"div",171),M(5,TN,5,1,"div",171),M(6,AN,5,1,"div",171),G(7,NN,5,2,"div",171,De().trackParam,!0),M(9,RN,5,1,"div",171),d(),M(10,VN,3,2,"div",158),M(11,jN,5,1,"div",172),d()),n&2){let e=m(2).$implicit,o=m(3).$implicit,i=m(4);g(2),b(i.getActionTargetText(e.data)?2:-1),g(),b(i.getActionInputText(e.data)?3:-1),g(),b(i.getActionCoords(e.data)?4:-1),g(),b(i.getActionBounds(e.data)?5:-1),g(),b(i.getActionClass(e.data)?6:-1),g(),q(i.getActionExtraParams(e.data)),g(2),b(!i.getActionTargetText(e.data)&&!i.getActionInputText(e.data)&&!i.getActionCoords(e.data)&&!i.getActionBounds(e.data)&&!i.getActionClass(e.data)&&i.getActionExtraParams(e.data).length===0?9:-1),g(),b(i.getStepPreImageUrl(o.data,e.data)||i.getStepPostImageUrl(o.data,e.data)?10:-1),g(),b(i.isActionFailed(e.data,o.data)?11:-1)}}function zN(n,t){if(n&1){let e=L();l(0,"div",167)(1,"div",144),k("click",function(i){v(e);let r=m().$index,a=m(3).$implicit,s=m(4);return y(s.toggleActionExpanded("action-"+a.id+"-"+r,i))}),l(2,"span",168),u(3),d(),l(4,"span",146),u(5),d(),M(6,yN,4,0,"span",169),l(7,"span",149),u(8),d()(),M(9,kN,4,3,"div",150),M(10,$N,12,8,"div",151),d()}if(n&2){let e=m(),o=e.$implicit,i=e.$index,r=m(3).$implicit,a=m(4);w("is-running",a.isItemRunning(o,r))("is-failed",a.isActionFailed(o.data,r.data))("is-expanded",a.isActionExpanded("action-"+r.id+"-"+i)),g(2),w("is-failed",a.isActionFailed(o.data,r.data)),g(),O(a.getActionIcon(o.data)),g(2),O(a.getActionTitle(o.data)),g(),b(a.isActionFailed(o.data,r.data)?6:-1),g(2),V(" ",a.isActionExpanded("action-"+r.id+"-"+i)?"expand_less":"expand_more"," "),g(),b(!a.isActionExpanded("action-"+r.id+"-"+i)&&(a.getActionTargetText(o.data)||a.getActionInputText(o.data)||a.getActionCoords(o.data))?9:-1),g(),b(a.isActionExpanded("action-"+r.id+"-"+i)?10:-1)}}function BN(n,t){n&1&&u(0),n&2&&V(" \xB7 Retrying in ",t,"\u2026 ")}function HN(n,t){if(n&1&&(l(0,"span",112),u(1," LLM temporarily unavailable "),M(2,BN,1,1),d(),l(3,"span",183),u(4," Frequent retries may indicate a low API rate limit. Consider increasing the limit or reducing request concurrency. "),d()),n&2){let e,o=m().$implicit,i=m(10);g(2),b((e=i.getLLMRetryEntryDelay(o))?2:-1,e)}}function UN(n,t){if(n&1&&(l(0,"span",112),u(1),d()),n&2){let e=m().$implicit,o=m(10);g(),V(" Retrying LLM request \xB7 Waited ",o.getLLMRetryEntryWaited(e)," ")}}function GN(n,t){if(n&1&&(l(0,"div",109),$(1,"span",110),l(2,"div",111),M(3,HN,5,1),M(4,UN,2,1,"span",112),d()()),n&2){let e=t.$implicit,o=t.$index,i=m(3).$implicit,r=m(7);g(),w("streaming",o===r.getLLMRetryEntries(i.data).length-1&&r.isLLMRetryEntryWaiting(e)),g(2),b(r.isLLMRetryEntryWaiting(e)?3:-1),g(),b(r.isLLMRetryEntryWaiting(e)?-1:4)}}function qN(n,t){if(n&1&&(l(0,"div",108),G(1,GN,5,4,"div",109,Ie),d()),n&2){let e=m(2).$implicit,o=m(7);g(),q(o.getLLMRetryEntries(e.data))}}function WN(n,t){n&1&&(l(0,"span",187)(1,"span",148),u(2,"pause_circle"),d(),u(3," Paused "),d())}function KN(n,t){n&1&&(l(0,"span",169)(1,"span",148),u(2,"warning"),d(),u(3," Failed "),d())}function YN(n,t){if(n&1&&(l(0,"span",149),u(1),d()),n&2){let e=m(3).$index,o=m(3).$implicit,i=m(4);g(),V(" ",i.isActionExpanded("llm-failure-"+o.id+"-"+e)?"expand_less":"expand_more"," ")}}function ZN(n,t){n&1&&(l(0,"div",152)(1,"span",153),u(2,"Waited:"),d(),l(3,"span",154),u(4),d()()),n&2&&(g(4),O(t))}function QN(n,t){if(n&1&&(l(0,"div",191)(1,"span"),u(2),d(),l(3,"span"),u(4),d()()),n&2){let e=t.$implicit,o=t.$index,i=m(11);g(2),V("Retry ",o+1),g(2),We(" Planned ",i.getLLMRetryEntryDelay(e)||"0s"," \xB7 Waited ",i.getLLMRetryEntryWaited(e)," ")}}function JN(n,t){if(n&1&&(l(0,"div",188)(1,"div",190),u(2,"Retry details"),d(),G(3,QN,5,3,"div",191,Ie),d()),n&2){let e=m(3).$implicit,o=m(7);g(3),q(o.getLLMFailureRetryEntries(e.data))}}function XN(n,t){if(n&1){let e=L();l(0,"div",189)(1,"button",192),k("click",function(i){v(e);let r=m(10);return y(r.resumePausedTask(i))}),l(2,"span",193),u(3,"play_arrow"),d(),l(4,"span"),u(5,"Continue task"),d(),l(6,"span",194),u(7,"arrow_forward"),d()(),l(8,"span",195),u(9,"Task paused \xB7 no steps are running"),d()()}}function eR(n,t){if(n&1){let e=L();l(0,"div",180)(1,"div",184),k("click",function(i){v(e);let r=m(2),a=r.$implicit,s=r.$index,c=m(3).$implicit,p=m(4);return y(p.hasLLMFailureRetryEntries(a.data)&&p.toggleActionExpanded("llm-failure-"+c.id+"-"+s,i))}),l(2,"div",185)(3,"span",186),u(4,"error"),d(),l(5,"span",146),u(6,"LLM Request Failed"),d(),M(7,WN,4,0,"span",187)(8,KN,4,0,"span",169),M(9,YN,2,1,"span",149),d(),l(10,"div",150)(11,"div",152)(12,"span",153),u(13,"What happened:"),d(),l(14,"span",154),u(15,"The AI request could not be completed after automatic retries."),d()(),l(16,"div",152)(17,"span",153),u(18,"Failure reason:"),d(),l(19,"span",154),u(20),d()(),M(21,ZN,5,1,"div",152),M(22,JN,5,0,"div",188),d()(),M(23,XN,10,0,"div",189),d()}if(n&2){let e,o=m(2),i=o.$implicit,r=o.$index,a=m(3).$implicit,s=m(4);g(),w("has-details",s.hasLLMFailureRetryEntries(i.data))("is-expanded",s.isActionExpanded("llm-failure-"+a.id+"-"+r)),g(6),b(s.isTerminalLLMFailure(i.data)?7:8),g(2),b(s.hasLLMFailureRetryEntries(i.data)?9:-1),g(11),O(s.getLLMErrorText(i.data)),g(),b((e=s.getLLMFailureWaited(i.data))?21:-1,e),g(),b(s.isActionExpanded("llm-failure-"+a.id+"-"+r)?22:-1),g(),b(s.isTerminalLLMFailure(i.data)?23:-1)}}function tR(n,t){if(n&1){let e=L();l(0,"span",200),k("click",function(){let i=v(e),r=m(10);return y(r.onToolKeyClick(i))}),l(1,"span",53),u(2,"draft"),d(),u(3),d()}n&2&&(g(3),V(" ",t," "))}function nR(n,t){n&1&&(l(0,"span",202),u(1),d()),n&2&&(g(),V("\xB7 ",t))}function oR(n,t){if(n&1){let e=L();l(0,"span",201),k("click",function(){v(e);let i=m(3).$implicit,r=m(7);return y(r.onVideoToolClick(i.data))}),l(1,"span",53),u(2,"videocam"),d(),u(3),d(),M(4,nR,2,1,"span",202)}if(n&2){let e,o=m(3).$implicit,i=m(7);R("title","Open recording at "+i.getVideoAnalysisRangeLabel(o.data)),g(3),V(" ",i.getVideoAnalysisRangeLabel(o.data)," "),g(),b((e=i.getVideoAnalysisDetail(o.data))?4:-1,e)}}function iR(n,t){if(n&1&&(l(0,"div",196)(1,"div",197),$(2,"span",110),l(3,"div",198)(4,"span"),u(5),d(),M(6,tR,4,1,"span",199),M(7,oR,5,3),d()()()),n&2){let e,o=m(2).$implicit,i=m(3).$implicit,r=m(4);w("is-running",r.isItemRunning(o,i))("is-note-tool",r.isNoteTool(o.data))("is-video-tool",r.isVideoTool(o.data))("is-video-attention",r.isVideoTool(o.data)&&r.isVideoAnalysisAttention(o.data)),g(2),w("streaming",r.isItemRunning(o,i)),g(3),We("",r.isVideoTool(o.data)?r.getVideoAnalysisLabel(o.data):r.getToolDisplayLabel(o.data),"",r.isNoteTool(o.data)&&r.getToolKey(o.data)||r.isVideoTool(o.data)?":":""),g(),b((e=r.isNoteTool(o.data)&&r.getToolKey(o.data))?6:-1,e),g(),b(r.isVideoTool(o.data)?7:-1)}}function rR(n,t){if(n&1&&(l(0,"span",205),u(1),d()),n&2){let e=m(3).$implicit,o=m(7);w("is-outputter",o.getToolAgentName(e.data)==="Outputter"),g(),O(o.getToolAgentName(e.data))}}function aR(n,t){n&1&&(l(0,"span",169)(1,"span",148),u(2,"error"),d(),u(3," Failed "),d())}function sR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Command:"),d(),l(3,"span",206)(4,"code"),u(5),d()()()),n&2){let e=m(5).$implicit,o=m(7);g(5),O(o.getAdbCommandLine(e.data))}}function cR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Cwd:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getAdbCwd(e.data),'"')}}function lR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Terminal ID:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getAdbTerminalId(e.data),'"')}}function dR(n,t){if(n&1&&(M(0,sR,6,1,"div",152),M(1,cR,5,1,"div",152),M(2,lR,5,1,"div",152)),n&2){let e=m(4).$implicit,o=m(7);b(o.getAdbCommandLine(e.data)?0:-1),g(),b(o.getAdbCwd(e.data)?1:-1),g(),b(o.getAdbTerminalId(e.data)?2:-1)}}function uR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Target:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolTargetText(e.data),'"')}}function gR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(2),V("",o.getToolInputLabel(e.data),":"),g(2),V('"',o.getToolInputText(e.data),'"')}}function pR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Coordinates:"),d(),l(3,"span",170),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),O(o.getToolCoords(e.data))}}function fR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Analysis:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolAnalysisText(e.data),'"')}}function mR(n,t){if(n&1&&(l(0,"div",152)(1,"span",153),u(2,"Details:"),d(),l(3,"span",154),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolGenericDetails(e.data),'"')}}function _R(n,t){if(n&1&&(M(0,uR,5,1,"div",152),M(1,gR,5,2,"div",152),M(2,pR,5,1,"div",152),M(3,fR,5,1,"div",152),M(4,mR,5,1,"div",152)),n&2){let e=m(4).$implicit,o=m(7);b(o.getToolTargetText(e.data)?0:-1),g(),b(o.getToolInputText(e.data)?1:-1),g(),b(o.getToolCoords(e.data)?2:-1),g(),b(o.getToolAnalysisText(e.data)?3:-1),g(),b(o.getToolGenericDetails(e.data)?4:-1)}}function hR(n,t){if(n&1&&(l(0,"div",150),M(1,dR,3,3),M(2,_R,5,5),d()),n&2){let e=m(3).$implicit,o=m(7);g(),b(o.isAdbCommandTool(e.data)?1:-1),g(),b(o.isAdbCommandTool(e.data)?-1:2)}}function CR(n,t){if(n&1&&(l(0,"div",157)(1,"span",159),u(2,"Command"),d(),l(3,"span",207)(4,"code"),u(5),d()()()),n&2){let e=m(5).$implicit,o=m(7);g(5),O(o.getAdbCommandLine(e.data))}}function MR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Working Directory"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getAdbCwd(e.data),'"')}}function bR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Terminal ID"),d(),l(3,"span",174),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),O(o.getAdbTerminalId(e.data))}}function PR(n,t){if(n&1&&(M(0,CR,6,1,"div",157),M(1,MR,5,1,"div",171),M(2,bR,5,1,"div",171)),n&2){let e=m(4).$implicit,o=m(7);b(o.getAdbCommandLine(e.data)?0:-1),g(),b(o.getAdbCwd(e.data)?1:-1),g(),b(o.getAdbTerminalId(e.data)?2:-1)}}function OR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Target"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolTargetText(e.data),'"')}}function vR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(2),O(o.getToolInputLabel(e.data)),g(2),V('"',o.getToolInputText(e.data),'"')}}function yR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Coordinates"),d(),l(3,"span",174),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),O(o.getToolCoords(e.data))}}function xR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Analysis"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolAnalysisText(e.data),'"')}}function wR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2,"Details"),d(),l(3,"span",173),u(4),d()()),n&2){let e=m(5).$implicit,o=m(7);g(4),V('"',o.getToolGenericDetails(e.data),'"')}}function SR(n,t){if(n&1&&(l(0,"div",171)(1,"span",159),u(2),d(),l(3,"span",174),u(4),d()()),n&2){let e=t.$implicit;g(2),O(e.key),g(2),O(e.value)}}function kR(n,t){if(n&1&&(M(0,OR,5,1,"div",171),M(1,vR,5,2,"div",171),M(2,yR,5,1,"div",171),M(3,xR,5,1,"div",171),M(4,wR,5,1,"div",171),G(5,SR,5,2,"div",171,De().trackParam,!0)),n&2){let e=m(4).$implicit,o=m(7);b(o.getToolTargetText(e.data)?0:-1),g(),b(o.getToolInputText(e.data)?1:-1),g(),b(o.getToolCoords(e.data)?2:-1),g(),b(o.getToolAnalysisText(e.data)?3:-1),g(),b(o.getToolGenericDetails(e.data)?4:-1),g(),q(o.getToolExtraParams(e.data))}}function ER(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Pre-Action (Before)"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(5).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPreImageUrl(a.data,r.data),r.data,i))}),l(4,"div",164)(5,"img",175,2),k("load",function(){v(e);let i=Io(6),r=Io(8),a=m(5).$implicit,s=m(7);return y(s.onImageLoad(i,r,a.data))}),d(),$(7,"div",176,3),d()()()}if(n&2){let e=m(5).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPreImageUrl(o.data,e.data),kt)}}function DR(n,t){if(n&1){let e=L();l(0,"div",161)(1,"span",162),u(2,"Post-Action (After)"),d(),l(3,"div",163),k("click",function(i){v(e);let r=m(5).$implicit,a=m(3).$implicit,s=m(4);return y(s.openImageModal(s.getStepPostImageUrl(a.data,r.data),null,i))}),l(4,"div",164),$(5,"img",177),d()()()}if(n&2){let e=m(5).$implicit,o=m(3).$implicit,i=m(4);g(5),R("src",i.getStepPostImageUrl(o.data,e.data),kt)}}function IR(n,t){if(n&1&&(l(0,"div",158),M(1,ER,9,1,"div",161),M(2,DR,6,1,"div",161),d()),n&2){let e=m(4).$implicit,o=m(3).$implicit,i=m(4);g(),b(i.getStepPreImageUrl(o.data,e.data)?1:-1),g(),b(i.getStepPostImageUrl(o.data,e.data)?2:-1)}}function TR(n,t){if(n&1&&(l(0,"div",172)(1,"span",178),u(2,"error"),d(),l(3,"span",179),u(4),d()()),n&2){let e=m(4).$implicit,o=m(7);g(4),O(o.getToolErrorMessage(e.data))}}function AR(n,t){if(n&1&&(l(0,"div",155),k("click",function(o){return o.stopPropagation()}),l(1,"div",156),M(2,PR,3,3),M(3,kR,7,5),d(),M(4,IR,3,2,"div",158),M(5,TR,5,1,"div",172),d()),n&2){let e=m(3).$implicit,o=m(3).$implicit,i=m(4);g(2),b(i.isAdbCommandTool(e.data)?2:-1),g(),b(i.isAdbCommandTool(e.data)?-1:3),g(),b(i.getStepPreImageUrl(o.data,e.data)||i.getStepPostImageUrl(o.data,e.data)?4:-1),g(),b(i.isToolFailed(e.data)?5:-1)}}function NR(n,t){if(n&1){let e=L();l(0,"div",203)(1,"div",144),k("click",function(i){v(e);let r=m(2).$index,a=m(3).$implicit,s=m(4);return y(s.toggleActionExpanded("tool-"+a.id+"-"+r,i))}),l(2,"span",168),u(3),d(),l(4,"span",146),u(5),d(),M(6,rR,2,3,"span",204),M(7,aR,4,0,"span",169),l(8,"span",149),u(9),d()(),M(10,hR,3,2,"div",150),M(11,AR,6,4,"div",151),d()}if(n&2){let e=m(2),o=e.$implicit,i=e.$index,r=m(3).$implicit,a=m(4);w("is-running",a.isItemRunning(o,r))("has-no-details",a.isAdbCommandTool(o.data)?!a.getAdbCommandLine(o.data):!a.getToolTargetText(o.data)&&!a.getToolInputText(o.data)&&!a.getToolCoords(o.data)&&!a.getToolAnalysisText(o.data)&&!a.getToolGenericDetails(o.data)&&a.getToolExtraParams(o.data).length===0)("is-failed",a.isToolFailed(o.data))("is-expanded",a.isActionExpanded("tool-"+r.id+"-"+i)),g(2),w("is-failed",a.isToolFailed(o.data)),g(),O(a.getToolIcon(o.data)),g(2),O(a.getToolTitle(o.data)),g(),b(a.getToolAgentName(o.data)?6:-1),g(),b(a.isToolFailed(o.data)?7:-1),g(2),V(" ",a.isActionExpanded("tool-"+r.id+"-"+i)?"expand_less":"expand_more"," "),g(),b(!a.isActionExpanded("tool-"+r.id+"-"+i)&&(a.isAdbCommandTool(o.data)?a.getAdbCommandLine(o.data):a.getToolTargetText(o.data)||a.getToolInputText(o.data)||a.getToolCoords(o.data)||a.getToolAnalysisText(o.data)||a.getToolGenericDetails(o.data))?10:-1),g(),b(a.isActionExpanded("tool-"+r.id+"-"+i)?11:-1)}}function RR(n,t){if(n&1&&(M(0,qN,3,0,"div",108),M(1,eR,24,10,"div",180),M(2,iR,8,14,"div",181),M(3,NR,12,17,"div",182)),n&2){let e=m().$implicit,o=m(3).$implicit,i=m(4);b(i.isLLMRetry(e.data)?0:-1),g(),b(i.isDisplayableLLMFailure(e.data)?1:-1),g(),b(i.shouldShowTool(e.data,o.data)&&!i.isDeviceActionTool(e.data)?2:-1),g(),b(i.shouldShowTool(e.data,o.data)&&i.isDeviceActionTool(e.data)?3:-1)}}function FR(n,t){if(n&1&&(M(0,pN,7,7,"div",131),M(1,fN,6,5,"div",132),M(2,mN,7,7,"div",131),M(3,_N,6,5,"div",132),M(4,vN,14,19,"div",133),M(5,zN,11,14,"div",134),M(6,RR,4,4)),n&2){let e=t.$implicit,o=m(3).$implicit,i=m(4);b(i.isCheckerBlock(o)&&e.type==="thinking"?0:-1),g(),b(i.isCheckerBlock(o)&&e.type==="text"&&i.isCheckerTextVisible(e)?1:-1),g(),b(!i.isCheckerBlock(o)&&e.type==="thinking"?2:-1),g(),b(!i.isCheckerBlock(o)&&e.type==="text"&&i.getRawThinking(o)?3:-1),g(),b(i.isReportStatusAction(e.data)?4:-1),g(),b(e.type==="action"&&i.isAndroidAction(e.data)?5:-1),g(),b(e.type==="tool"?6:-1)}}function LR(n,t){if(n&1&&(l(0,"div",107),G(1,FR,7,7,null,null,De().trackStepEvent,!0),d()),n&2){let e=m(2).$implicit,o=m(4);g(),q(o.getSortedStepEvents(e.data))}}function VR(n,t){if(n&1&&(l(0,"span",213),u(1),d()),n&2){let e=m().$implicit;g(),O(e.suggestion)}}function jR(n,t){if(n&1&&(l(0,"div",208)(1,"span",128),u(2),d(),l(3,"span",210),u(4),d(),l(5,"span",211)(6,"span",212),u(7),d(),M(8,VR,2,1,"span",213),d()()),n&2){let e=t.$implicit,o=t.$index,i=m(3).$implicit,r=m(4);R("ngClass",e.status),g(2),O(r.getVerdictItemNumber(i,e,o)),g(2),O(r.getVerdictIcon(e.status)),g(3),O(e.evidence||r.getVerdictStatusText(e.status)),g(),b(e.suggestion?8:-1)}}function $R(n,t){if(n&1&&(l(0,"div",209),u(1),d()),n&2){let e=t.$implicit;g(),O(e)}}function zR(n,t){if(n&1&&(l(0,"div",121),G(1,jR,9,5,"div",208,Do),G(3,$R,2,1,"div",209,Ie),d()),n&2){let e=m(2).$implicit,o=m(4);R("ngClass","tone-"+o.getCheckerTone(e)),g(),q(o.getCheckerVerdicts(e)),g(2),q(o.getCheckerNotes(e))}}function BR(n,t){if(n&1&&(l(0,"div",119)(1,"div",106),M(2,gN,9,7,"div",120),M(3,LR,3,0,"div",107),M(4,zR,5,1,"div",121),d()()),n&2){let e=m().$implicit,o=m(4);R("ngClass",Up(4,qT,"type-"+e.type)),g(2),b(o.isCheckerBlock(e)?2:-1),g(),b(o.getSortedStepEvents(e.data).length>0?3:-1),g(),b(o.hasCheckerResult(e)?4:-1)}}function HR(n,t){if(n&1&&M(0,BR,5,6,"div",119),n&2){let e=t.$implicit,o=m(4);b(o.hasVisibleContent(e)?0:-1)}}function UR(n,t){if(n&1&&(l(0,"div",114),G(1,HR,1,1,null,null,De().trackBlock,!0),d()),n&2){let e=m().$implicit;g(),q(e.blocks)}}function GR(n,t){if(n&1&&(l(0,"div",99),M(1,cN,4,5,"div",113),M(2,UR,3,0,"div",114),d()),n&2){let e=t.$implicit,o=m(2);g(),b(o.hasVisibleBlocks(e)?1:-1),g(),b(o.hasVisibleBlocks(e)?2:-1)}}function qR(n,t){if(n&1&&(l(0,"h3",222),u(1),d()),n&2){let e=m();g(),O(e.title)}}function WR(n,t){if(n&1&&(l(0,"strong"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function KR(n,t){if(n&1&&(l(0,"code"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function YR(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function ZR(n,t){if(n&1&&(M(0,WR,2,1,"strong"),M(1,KR,2,1,"code"),M(2,YR,2,1,"span")),n&2){let e=t.$implicit;b(e.bold?0:-1),g(),b(e.code?1:-1),g(),b(!e.bold&&!e.code?2:-1)}}function QR(n,t){if(n&1&&(l(0,"div",226)(1,"span",148),u(2),d(),l(3,"span",63),G(4,ZR,3,3,null,null,Ie),d()()),n&2){let e=t.$implicit;Dl("status-"+e.type),g(2),V(" ",e.type==="checked"?"check_circle":e.type==="progress"?"pending":"radio_button_unchecked"," "),g(2),q(e.segments)}}function JR(n,t){if(n&1&&(l(0,"div",223),G(1,QR,6,3,"div",225,De().trackMilestone,!0),d()),n&2){let e=m();g(),q(e.milestones)}}function XR(n,t){if(n&1&&(l(0,"span",231),u(1),d()),n&2){let e=m().$implicit;w("assert",e.checkKind==="assert"),g(),We("",e.checkKind||"verify","",e.atEnd?"@end":"")}}function e3(n,t){n&1&&(l(0,"span",230),u(1,"finding"),d())}function t3(n,t){if(n&1&&(l(0,"strong"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function n3(n,t){if(n&1&&(l(0,"code"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function o3(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=m().$implicit;g(),O(e.text)}}function i3(n,t){if(n&1&&(M(0,t3,2,1,"strong"),M(1,n3,2,1,"code"),M(2,o3,2,1,"span")),n&2){let e=t.$implicit;b(e.bold?0:-1),g(),b(e.code?1:-1),g(),b(!e.bold&&!e.code?2:-1)}}function r3(n,t){if(n&1&&(l(0,"p",228),M(1,XR,2,4,"span",229),M(2,e3,2,0,"span",230),G(3,i3,3,3,null,null,Ie),d()),n&2){let e=t.$implicit;Dl("line-"+e.type),g(),b(e.type==="verify"?1:-1),g(),b(e.type==="finding"?2:-1),g(),q(e.segments)}}function a3(n,t){if(n&1&&(l(0,"div",224),G(1,r3,5,4,"p",227,De().trackMarkdownLine,!0),d()),n&2){let e=m();g(),q(e.otherLines)}}function s3(n,t){if(n&1&&(l(0,"div",221),M(1,qR,2,1,"h3",222),M(2,JR,3,0,"div",223),M(3,a3,3,0,"div",224),d()),n&2){let e=t;g(),b(e.title?1:-1),g(),b(e.milestones.length>0?2:-1),g(),b(e.otherLines.length>0?3:-1)}}function c3(n,t){if(n&1){let e=L();l(0,"div",100)(1,"div",214)(2,"div",215)(3,"span",216),u(4,"assignment_turned_in"),d(),l(5,"span",217),u(6,"Task Report"),d()(),l(7,"div",218)(8,"button",219),k("click",function(i){v(e);let r=m(2);return y(r.openOutputterNote(i))}),l(9,"span",22),u(10,"visibility"),d(),l(11,"span"),u(12,"View in Notes"),d()()()(),l(13,"div",220),M(14,s3,4,3,"div",221),d()()}if(n&2){let e,o=m(2);g(14),b((e=o.parsedOutputReport())?14:-1,e)}}function l3(n,t){if(n&1&&(l(0,"div",101)(1,"div",232)(2,"span",96),u(3,"progress_activity"),d(),l(4,"span",233),u(5),d()()()),n&2){let e=m(2);g(5),O(e.currentPlanningText())}}function d3(n,t){if(n&1&&(l(0,"div",19),M(1,aN,11,1,"div",98),G(2,GR,3,2,"div",99,De().trackPhase,!0),M(4,c3,15,1,"div",100),M(5,l3,6,1,"div",101),d()),n&2){let e,o=m();g(),b(o.startupWorkItems().length>0?1:-1),g(),q(o.phases()),g(2),b((e=o.outputterReport())?4:-1,e),g(),b(o.showPlanningLoader()&&(o.startupWorkItems().length===0||o.startupPreparationIsComplete()||o.consolidatedBlocks().length>0)?5:-1)}}function u3(n,t){if(n&1){let e=L();l(0,"div",234),k("click",function(i){v(e);let r=m();return y(r.closeImageModal(i))}),l(1,"div",235),k("click",function(i){return i.stopPropagation()}),l(2,"button",236),k("click",function(i){v(e);let r=m();return y(r.closeImageModal(i))}),l(3,"span",22),u(4,"close"),d()(),l(5,"div",164)(6,"img",237,4),k("load",function(){v(e);let i=Io(7),r=Io(9),a=m();return y(a.onImageLoad(i,r,a.selectedZoomAction()))}),d(),$(8,"div",176,5),d()()()}if(n&2){let e=m();g(6),R("src",e.selectedZoomImage(),kt)}}var Es=["Planning next step...","Analyzing screen coordinates...","Consulting neural network...","Formulating tactical action plan...","Deciphering UI state & elements...","Synthesizing decision pathways...","Calibrating next move...","Aligning logical vectors...","Evaluating optimal sub-goals...","Gathering sensory inputs...","Computing next interaction...","Optimizing execution strategy...","Simulating probable outcomes...","Summoning AI intuition...","Brewing the next command...","Strategizing tactical moves..."],oO=[{started:"device_check",completed:"device_ready",completedMessage:"Android device connected"},{started:"uiautomator",completed:"uiautomator_ready",completedMessage:"UI Automator is ready"},{started:"environment",completed:"environment_ready",completedMessage:"Device environment is ready"}];function g3(n){let t=Math.max(0,n);return t<10?`${t.toFixed(1)}s`:`${Math.round(t)}s`}function p3(n,t,e,o){let i=new Map(n.map(a=>[a.stage,a])),r=i.get("first_response");return oO.flatMap((a,s)=>{let c=i.get(a.started),p=i.get(a.completed);if(!c&&!p)return[];let f=oO.slice(s+1).map(A=>i.get(A.started)||i.get(A.completed)).find(A=>!!A),_=a.started==="environment"?r:f,C=p||_,h=!C&&!e&&o,P=c?.timestamp||p?.timestamp||t,T=C?.timestamp||(h?t:n[n.length-1]?.timestamp||P);return[F(S({},p||c),{message:p?.message||(C?a.completedMessage:c.message),isActive:h,elapsed:g3(T-P)})]})}var Wd=class n{agentService=D(It);http=D(Ro);destroyRef=D(Ae);zone=D(ge);isUserAtBottom=!0;resizeObserver=null;streamLogsContainer=null;autoScrollTimer=null;autoScrollStreamBoxes=!1;onContainerScroll=()=>{let t=this.streamLogsContainer;if(!t)return;let e=150,o=t.scrollHeight-t.scrollTop-t.clientHeight;this.isUserAtBottom=o<=e};currentPlanningText=x(Es[0]);planningIntervalId=null;currentPhraseIndex=0;isTaskDropdownOpen=x(!1);isNotesDropdownOpen=x(!1);selectedZoomImage=x(null);selectedZoomAction=x(null);collapsedStreams=x(new Set);scheduledCollapses=new Set;typedTextsSignal=x({});typingTargets=new Map;typingRafId=null;lastTypingTimestamp=0;static TYPING_CHARS_PER_SECOND=667;static TYPING_MIN_FRAME_MS=28;markdownHtmlCache=new Map;thinkingTextCache=new WeakMap;deviceSerialCache=new WeakMap;expandedActionCards=x(new Set);collapsedActionCards=x(new Set);retryClock=x(Date.now());actionParamsCache=new WeakMap;toolParamsCache=new WeakMap;sortedEventsCache=new WeakMap;activeQueue=I(()=>this.agentService.sessions().filter(e=>{let o=this.getTaskStatus(e);return o==="running"||o==="paused"||o==="pending"}).sort((e,o)=>{let i=this.getTaskStatus(e),r=this.getTaskStatus(o),a=i==="running"||i==="paused",s=r==="running"||r==="paused";return a&&!s?-1:!a&&s?1:(e.start_time||0)-(o.start_time||0)}));historyTasks=I(()=>this.agentService.sessions().filter(t=>{let e=this.getTaskStatus(t);return e==="completed"||e==="failed"||e==="cancelled"}));currentNoteContent=I(()=>{let t=this.agentService.currentNotes(),e=this.agentService.selectedNoteKey();return t[e]||""});noteKeys=I(()=>Object.keys(this.agentService.currentNotes()).filter(t=>t.toLowerCase().endsWith(".md")));outputterReport=I(()=>this.agentService.currentNotes()["output.md"]||null);parsedOutputReport=I(()=>{let t=this.outputterReport();return t?ks(t):null});parsedNote=I(()=>this.getParsedNote(this.currentNoteContent()));filteredLogs=I(()=>{let t=this.agentService.sessionLogs(),e=this.agentService.currentSessionId();return e?t.filter(o=>{let i=o.session_id||o.data?.session_id;return!i||i===e}):t});sessionSummary=I(()=>eO(this.filteredLogs()));showPlanningLoader=I(()=>{let t=this.agentService.agentStatus()==="running",e=this.agentService.currentSessionId(),o=this.agentService.runningSessionId(),i=!e||e===o;return nO(this.filteredLogs(),t,i)});isViewingPausedTask=I(()=>{if(!this.agentService.isPaused()||this.agentService.agentStatus()!=="paused")return!1;let t=this.agentService.currentSessionId(),e=this.agentService.runningSessionId();return!t||t===e});sessionDuration=I(()=>{let t=this.agentService.sessions().find(e=>e.session_id===this.agentService.currentSessionId());return tO(t?.start_time||0,this.filteredLogs())});consolidatedBlocks=I(()=>ZP(this.filteredLogs()));phases=I(()=>{let t=this.agentService.sessions().find(e=>e.session_id===this.agentService.currentSessionId());return JP(this.consolidatedBlocks(),t?.start_time||0)});startupWorkItems=I(()=>{this.retryClock();let t=this.agentService.currentStartupProgress(),e=["running","paused"].includes(this.agentService.agentStatus());return p3(t,Date.now()/1e3,this.consolidatedBlocks().length>0,e)});startupPreparationIsRunning=I(()=>this.startupWorkItems().some(t=>t.isActive));startupPreparationIsComplete=I(()=>{let t=this.startupWorkItems();return t.length>0&&!t.some(e=>e.isActive)});startupPreparationDuration=I(()=>{let t=this.startupWorkItems();if(t.length===0)return 0;let e=this.agentService.currentStartupProgress(),o=e[0]?.timestamp||t[0].timestamp,i=e.find(a=>a.stage==="environment_ready")?.timestamp||e.find(a=>a.stage==="first_response")?.timestamp,r=this.startupPreparationIsRunning()?Date.now()/1e3:i||Math.max(...t.map(a=>a.timestamp));return Math.max(1,Math.round(r-o))});retryablePauseTrace=I(()=>{if(!this.isViewingPausedTask())return null;let t=this.consolidatedBlocks();for(let e=t.length-1;e>=0;e--){let o=t[e]?.data?.generic_tools;if(Array.isArray(o))for(let i=o.length-1;i>=0;i--){let r=o[i];if(this.isDisplayableLLMFailure(r))return r}}return null});hasVisibleBlocks(t){return!t||!t.blocks?!1:t.blocks.some(e=>this.hasVisibleContent(e))}constructor(){this.zone.runOutsideAngular(()=>{let t=setInterval(()=>{let e=this.agentService.agentStatus();(e==="running"||e==="paused"||this.agentService.isRetrying())&&this.retryClock.set(Date.now())},1e3);this.destroyRef.onDestroy(()=>clearInterval(t))}),Ye(()=>{this.filteredLogs().find(o=>o.type==="llm_stream"&&!o.data?.isCompleted)&&this.scheduleAutoScroll(!0)}),Ye(()=>{this.consolidatedBlocks(),this.typedTextsSignal(),this.scheduleAutoScroll(!1)}),Ye(()=>{this.consolidatedBlocks().forEach(e=>{if(e.type==="checker")return;let o=e.id,i=this.getRawThinking(e)||"",r=this.getNativeThinking(e)||"";de(()=>{let a=this.typedTextsSignal();i&&(o in a?(a[o]||"").lengthF(S({},p),{[o]:i})):this.startTyping(o,i,!1)):e.data?.isCompleted?this.typedTextsSignal.update(c=>F(S({},c),{[o]:i})):(this.typedTextsSignal.update(c=>F(S({},c),{[o]:""})),this.startTyping(o,i,!1)));let s=o+"-native";if(r){let c=e.data?.execution_id||e.data?.step_id||o;s in a?(a[s]||"").lengthF(S({},f),{[s]:r})),this.collapsedStreams.update(f=>{let _=new Set(f);return _.add(c),_})):this.startTyping(s,r,!0,c)):e.data?.isCompleted?(this.typedTextsSignal.update(p=>F(S({},p),{[s]:r})),this.collapsedStreams.update(p=>{let f=new Set(p);return f.add(c),f})):(this.typedTextsSignal.update(p=>F(S({},p),{[s]:""})),this.startTyping(s,r,!0,c))}})})}),Ye(()=>{this.agentService.currentSessionId(),de(()=>{this.stopTypingLoop(),this.typedTextsSignal.set({}),this.markdownHtmlCache.clear(),this.scheduledCollapses.clear(),this.collapsedStreams.set(new Set),this.isUserAtBottom=!0,setTimeout(()=>{let t=document.querySelector(".stream-logs-content");t&&(t.scrollTop=t.scrollHeight)},100)})}),Ye(()=>{this.showPlanningLoader()?this.planningIntervalId||(this.currentPhraseIndex=Math.floor(Math.random()*Es.length),this.currentPlanningText.set(Es[this.currentPhraseIndex]),this.planningIntervalId=setInterval(()=>{this.currentPhraseIndex=(this.currentPhraseIndex+1)%Es.length,this.currentPlanningText.set(Es[this.currentPhraseIndex])},2800)):this.planningIntervalId&&(clearInterval(this.planningIntervalId),this.planningIntervalId=null)}),this.destroyRef.onDestroy(()=>{this.planningIntervalId&&(clearInterval(this.planningIntervalId),this.planningIntervalId=null),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.stopTypingLoop(),this.autoScrollTimer&&(clearTimeout(this.autoScrollTimer),this.autoScrollTimer=null),this.streamLogsContainer&&(this.streamLogsContainer.removeEventListener("scroll",this.onContainerScroll),this.streamLogsContainer=null)})}ngAfterViewInit(){let t=document.querySelector(".stream-logs-content");this.streamLogsContainer=t,t&&(this.zone.runOutsideAngular(()=>{t.addEventListener("scroll",this.onContainerScroll,{passive:!0})}),typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.isUserAtBottom&&this.scheduleAutoScroll(!1)}),this.resizeObserver.observe(t)))}scheduleAutoScroll(t){this.autoScrollStreamBoxes=this.autoScrollStreamBoxes||t,!this.autoScrollTimer&&this.zone.runOutsideAngular(()=>{this.autoScrollTimer=setTimeout(()=>{this.autoScrollTimer=null;let e=this.autoScrollStreamBoxes;this.autoScrollStreamBoxes=!1;let o=e?Array.from(document.getElementsByClassName("stream-box")):[],i=o.map(s=>s.scrollHeight),r=this.streamLogsContainer,a=r&&this.isUserAtBottom?r.scrollHeight:null;o.forEach((s,c)=>{s.scrollTop=i[c]}),r&&a!==null&&(r.scrollTop=a)},50)})}toggleTaskDropdown(t){t.stopPropagation(),this.isTaskDropdownOpen.update(e=>!e),this.isTaskDropdownOpen()&&this.isNotesDropdownOpen.set(!1)}toggleNotesDropdown(t){t.stopPropagation(),this.isNotesDropdownOpen.update(e=>!e),this.isNotesDropdownOpen()&&this.isTaskDropdownOpen.set(!1)}openOutputterNote(t){t&&t.stopPropagation(),this.agentService.selectedNoteKey.set("output.md"),this.isNotesDropdownOpen.set(!0),this.isTaskDropdownOpen.set(!1)}closeAllDropdowns(){this.isTaskDropdownOpen.set(!1),this.isNotesDropdownOpen.set(!1)}getTaskStatus(t){if(t.status){let e=t.status.toLowerCase();if(e==="completed"||e==="success"||e==="failed"||e==="cancelled")return e==="success"?"completed":e;if(e==="running"||e==="paused"||e==="pending")return e}return t.session_id===this.agentService.runningSessionId()&&(this.agentService.agentStatus()==="running"||this.agentService.agentStatus()==="paused")?this.agentService.agentStatus():"completed"}getDeviceSerial(t){if(this.deviceSerialCache.has(t))return this.deviceSerialCache.get(t)??null;let e=null,o=t.device_serial||t.device_id;if(o&&o!=="pending"&&o!=="null"&&o!=="undefined")e=o;else if(t.device_info)try{let i=typeof t.device_info=="string"?JSON.parse(t.device_info):t.device_info,r=i?.device_id||i?.device_serial;r&&r!=="pending"&&r!=="null"&&r!=="undefined"&&(e=r)}catch(i){}return this.deviceSerialCache.set(t,e),e}selectTask(t,e){e&&e.stopPropagation(),this.agentService.selectSession(t,!0),this.isTaskDropdownOpen.set(!1)}stopTask(t,e){e&&e.stopPropagation(),this.agentService.stopTask(t,!1)}deleteTask(t,e){e&&e.stopPropagation(),confirm("Are you sure you want to delete this task? This cannot be undone.")&&this.agentService.deleteSession(t).subscribe({error:o=>{console.error(`Failed to delete session ${t}:`,o)}})}selectNote(t,e){e&&e.stopPropagation(),this.agentService.selectedNoteKey.set(t)}getParsedNote(t){return ks(t)}getParsedNoteLines(t){return Ss(t)}startTyping(t,e,o=!1,i){let r=t.endsWith("-native")?t.replace("-native",""):t;this.typingTargets.set(t,{blockId:r,isThinking:o,execId:i,fallbackText:e}),this.ensureTypingLoop()}ensureTypingLoop(){this.typingRafId===null&&(this.lastTypingTimestamp=performance.now(),this.zone.runOutsideAngular(()=>{let t=e=>{this.typingRafId=null;let o=e-this.lastTypingTimestamp;o>=n.TYPING_MIN_FRAME_MS&&(this.lastTypingTimestamp=e,this.advanceTyping(o)),this.typingTargets.size>0&&(this.typingRafId=requestAnimationFrame(t))};this.typingRafId=requestAnimationFrame(t)}))}stopTypingLoop(){this.typingTargets.clear(),this.typingRafId!==null&&(cancelAnimationFrame(this.typingRafId),this.typingRafId=null)}advanceTyping(t){let e=de(()=>this.consolidatedBlocks()),o=de(()=>this.typedTextsSignal()),i=Math.max(1,Math.round(n.TYPING_CHARS_PER_SECOND*t/1e3)),r={},a=!1;for(let[s,c]of this.typingTargets){let p=e.find(C=>C.id===c.blockId),f=c.fallbackText;p&&(f=(c.isThinking?this.getNativeThinking(p):this.getRawThinking(p))||c.fallbackText);let _=o[s]||"";_.lengthS(S({},s),r))}triggerDelayedCollapse(t){this.scheduledCollapses.has(t)||(this.scheduledCollapses.add(t),setTimeout(()=>{this.collapsedStreams.update(e=>{let o=new Set(e);return o.add(t),o})},1e3))}onDetailsToggle(t,e){let i=t.target.open;this.collapsedStreams.update(r=>{let a=new Set(r);return i?a.delete(e):a.add(e),a})}toggleCollapse(t){this.collapsedStreams.update(e=>{let o=new Set(e);return o.has(t)?o.delete(t):o.add(t),o})}getActionObject(t){return ke(t)}isAndroidAction(t){return jn(t)}getActionIcon(t){return zd(t)}getActionTitle(t){return mm(t)}getActionTargetText(t){return _m(t)}getActionInputLabel(t){return AP(t)}getActionInputText(t){return NP(t)}getActionCoords(t){return hm(t)}isActionFailed(t,e){return Bd(t,e)}getActionErrorMessage(t,e){return RP(t,e)}isActionExpanded(t,e=!1){return this.collapsedActionCards().has(t)?!1:this.expandedActionCards().has(t)?!0:e}toggleActionExpanded(t,e,o=!1){e&&e.stopPropagation(),this.isActionExpanded(t,o)?(this.expandedActionCards.update(r=>{let a=new Set(r);return a.delete(t),a}),this.collapsedActionCards.update(r=>{let a=new Set(r);return a.add(t),a})):(this.collapsedActionCards.update(r=>{let a=new Set(r);return a.delete(t),a}),this.expandedActionCards.update(r=>{let a=new Set(r);return a.add(t),a}))}getActionBounds(t){return FP(t)}getActionResourceId(t){return LP(t)}getActionClass(t){return VP(t)}getActionExtraParams(t){return jP(t,this.actionParamsCache)}getToolExtraParams(t){return kP(t,this.toolParamsCache)}getStepPreImageUrl(t,e){return Cm(t,e)}getStepPostImageUrl(t,e){return Mm(t,e)}openImageModal(t,e,o){o&&o.stopPropagation(),this.selectedZoomImage.set(t),this.selectedZoomAction.set(e||null)}closeImageModal(t){t&&t.stopPropagation(),this.selectedZoomImage.set(null),this.selectedZoomAction.set(null)}onImageLoad(t,e,o){if(!(!t||!e))try{this.drawActionCoordinatesOnOverlay(t,e,o)}catch(i){console.warn("Failed to draw action overlay:",i)}}drawActionCoordinatesOnOverlay(t,e,o){Rd(t,e,o)}firstSaveNoteByKey=I(()=>{let t=new Map;for(let e of this.consolidatedBlocks())if(e.type==="step"&&e.data.generic_tools){for(let o of this.getUniqueGenericTools(e.data.generic_tools))if(o.name&&o.name.toLowerCase()==="save_note"){let i=this.getToolKey(o);i&&!t.has(i)&&t.set(i,o)}}return t});isFirstSaveNoteForKey(t){if(!t||!t.name||t.name.toLowerCase()!=="save_note")return!1;let e=this.getToolKey(t);if(!e)return!0;let o=this.firstSaveNoteByKey().get(e);return o?t.trace_id&&o.trace_id?t.trace_id===o.trace_id:t===o:!0}getToolDisplayLabel(t){return CP(t,this.isFirstSaveNoteForKey(t))}isNoteTool(t){return pP(t)}isVideoTool(t){return Fd(t)}getVideoToolTarget(t){return fP(t)}getVideoAnalysisLabel(t){return Fi(t)?.title||"Analyzing screen recording"}getVideoAnalysisRangeLabel(t){let e=Fi(t)?.requestedRange;return e?`${cm(e.start)}\u2013${cm(e.end)}`:"Screen recording"}getVideoAnalysisDetail(t){let e=Fi(t);return!e||e.totalCount<=1?"":e.outcome==="running"||e.outcome==="recovering"?e.completedCount>0?`${e.completedCount}/${e.totalCount} segments saved`:"":e.outcome==="partial"?`${e.completedCount}/${e.totalCount} segments saved`:""}isVideoAnalysisAttention(t){let e=Fi(t)?.outcome;return e==="partial"||e==="failed"}onVideoToolClick(t){let e=this.agentService.currentSessionId();if(e){let o=Fi(t)?.requestedRange?.start;this.agentService.openVideoPlayer(e,void 0,void 0,o)}}getScreenRecordingButtonTitle(){return this.agentService.isCurrentSessionRunning()?"Task is currently running (Recording screen)":this.agentService.currentSessionRecordingStatus()==="processing"?"Preparing screen recording...":this.agentService.currentSessionVideoUrl()?"Play Screen Recording Video":this.agentService.hasCurrentSessionStepFrames()?this.agentService.currentSessionRecordingStatus()==="failed"?"Video recording failed \u2014 Click to replay step-by-step screenshots":"Play step-by-step screenshots replay":this.agentService.currentSessionRecordingStatus()==="failed"?"Screen recording generation failed":"Screen Recording"}isDeviceActionTool(t){return Ld(t)}isFailureAnalyzerActionTool(t){return Ld(t)}getToolAgentName(t){return _P(t)}shouldShowTool(t,e){return mP(t,e)}getUniqueGenericTools(t){return Vd(t)}getSortedStepEvents(t){return XP(t,this.sortedEventsCache)}getThinkingTexts(t){let e=this.thinkingTextCache.get(t);if(!e){let o=t.operator_native_thinking||(t.stream_type==="thinking"?t.text:null),i=t.operator_raw_thinking||(t.stream_type==="text"?t.text:null);e={native:o&&o.trim()?o:null,raw:i&&i.trim()&&this.isHumanThinking(i)?i:null},this.thinkingTextCache.set(t,e)}return e}getNativeThinking(t){return!t||!t.data?null:this.getThinkingTexts(t.data).native}getRawThinking(t){return!t||!t.data?null:this.getThinkingTexts(t.data).raw}isCheckerBlock(t){return t?.type==="checker"}isCheckerPhase(t){let e=Array.isArray(t?.blocks)?t.blocks:[];return e.length>0&&e.every(o=>this.isCheckerBlock(o))}getCheckerPhaseLabel(t){switch(t?.data?.phase){case"final":return"Final check";case"outcome":return"Result";default:return"Check"}}getCheckerVerdicts(t){return Array.isArray(t?.data?.verdicts)?t.data.verdicts:[]}getCheckerItems(t){let e=t?.data||{};return Array.isArray(e.items)&&e.items.length>0?e.items:this.getCheckerVerdicts(t).map(o=>({kind:o.kind,text:o.item_text,when:o.when}))}getCheckMethodLabel(t,e){let o=[t?.kind==="assert"?"Test assertion":"Must pass"];return t?.when==="at_end"&&e?.data?.phase!=="final"&&o.push("at the end"),o.join(" \xB7 ")}isCheckerMuted(t){let e=t?.data?.status;return e==="superseded"||e==="unchecked"}getVerdictItemNumber(t,e,o){let i=this.getCheckerItems(t).findIndex(r=>r.text===e?.item_text);return(i>-1?i:o)+1}getVerdictStatusText(t){switch(t){case"passed":return"Passed";case"failed":return"Failed";case"inconclusive":return"Could not be confirmed";case"superseded":return"Superseded by a newer check";case"unchecked":return"Not checked";default:return t||""}}hasCheckerResult(t){return!this.isCheckerBlock(t)||this.isBlockRunning(t)||this.isCheckerMuted(t)?!1:t.data?.phase==="outcome"?this.getCheckerNotes(t).length>0:this.getCheckerVerdicts(t).length>0||this.getCheckerNotes(t).length>0}getCheckerNotes(t){let e=t?.data||{};if(e.phase==="outcome")return Array.isArray(e.last_findings)?e.last_findings:[];let o=[];if(Array.isArray(e.unmet_subgoals))for(let i of e.unmet_subgoals)o.push(`Not finished yet: ${i}`);return e.reverted&&o.push("The step was sent back to be redone."),e.error&&o.push(String(e.error)),o}openCheckSegments=x(new Set);isCheckerSegmentRunning(t,e){return this.isBlockRunning(t)?e===this.getSortedStepEvents(t.data).length-1:!1}isCheckerSegmentCollapsed(t,e){return this.isCheckerSegmentRunning(t,e)?!1:!this.openCheckSegments().has(`${t.id}-${e}`)}toggleCheckerSegment(t,e){let o=`${t.id}-${e}`;this.openCheckSegments.update(i=>{let r=new Set(i);return r.has(o)?r.delete(o):r.add(o),r})}isCheckerTextVisible(t){let e=t?.data?.text;return typeof e=="string"&&e.trim().length>0&&this.isHumanThinking(e)}getCheckerTone(t){let e=t?.data||{};if(e.phase==="outcome")return e.task_status==="completed"?"passed":e.task_status==="blocked"?"failed":"inconclusive";if(e.isCompleted===!1)return"running";if(e.status==="superseded"||e.status==="unchecked")return"muted";if(e.status==="error")return"inconclusive";let o=this.getCheckerVerdicts(t);return o.some(i=>i.status==="failed")?"failed":o.some(i=>i.status==="inconclusive")?"inconclusive":"passed"}getCheckerStatusLabel(t){let e=t?.data||{};if(e.phase==="outcome"){let r=e.tests||{},a=e.task_status==="completed"?"Goal completed":e.task_status==="blocked"?"Blocked":"Partially completed",s=[[r.passed,"passed"],[r.failed,"failed"],[r.inconclusive,"inconclusive"],[r.unchecked,"not checked"]].filter(([c])=>Number(c)>0).map(([c,p])=>`${c} ${p}`);return s.length>0?`${a} \xB7 ${s.join(" \xB7 ")}`:a}if(e.isCompleted===!1)return"Checking\u2026";switch(e.status){case"superseded":return"Superseded";case"unchecked":return"Not checked";case"error":return"No verdict"}let o=this.getCheckerVerdicts(t),i=o.filter(r=>r.status==="failed").length;return i>0?`${i} failed`:o.some(r=>r.status==="inconclusive")?"Inconclusive":o.length>0?"Passed":"Done"}getVerdictIcon(t){switch(t){case"passed":return"check_circle";case"failed":return"cancel";case"inconclusive":return"help";case"superseded":return"history";case"unchecked":return"radio_button_unchecked";default:return"pending"}}hasVisibleContent(t){if(!t)return!1;if(t.type==="checker")return!0;let e=!!this.getNativeThinking(t),o=!!this.getRawThinking(t),i=!!t.data?.generic_tools&&t.data.generic_tools.some(a=>this.shouldShowTool(a,t.data)||this.isDisplayableLLMFailure(a)||this.isLLMRetry(a)||this.isReportStatusAction(a)),r=!!t.data?.action_taken&&(this.isAndroidAction(t.data.action_taken)||this.isReportStatusAction(t.data.action_taken));return e||o||i||r}isReportStatusAction(t){return jo(t)}getReportStatusValue(t){return TP(t)}getReportStatusExplanation(t){return $d(t)}isHumanThinking(t){return EP(t)}isTerminalLLMFailure(t){return!!t&&this.retryablePauseTrace()===t}isDisplayableLLMFailure(t){return t?.type==="llm_call"&&t?.status==="failed"&&(t?.name==="llm_pause"||t?.payload?.pause===!0)}resumePausedTask(t){t.stopPropagation(),this.agentService.resumeTask()}getLLMErrorText(t){let e="The AI provider did not return error details after the request failed.";if(!t)return e;let o=t.payload?.error||t.error;if(!o)return e;let i=this.cleanErrorMessage(o);return i==="Unknown error"?e:i}isLLMRetry(t){return t?.type==="llm_call"&&t?.status==="retrying"&&t?.payload?.source==="provider_sdk"&&["google","gemini"].includes(String(t?.payload?.provider||"").toLowerCase())}getLLMRetryEntries(t){let e=t?.payload?.retries;return Array.isArray(e)&&e.length>0?e:[t?.payload||t]}getLLMFailureRetryEntries(t){let e=t?.payload?.retries;return Array.isArray(e)?e.filter(o=>o?.source==="provider_sdk"&&["google","gemini"].includes(String(o?.provider||"").toLowerCase())):[]}hasLLMFailureRetryEntries(t){return this.getLLMFailureRetryEntries(t).length>0}getLLMRetryEntryDelay(t){let e=Number(t?.delay);return!Number.isFinite(e)||e<=0?null:`${e.toFixed(2).replace(/\.00$/,"")}s`}getLLMRetryEntryWaited(t){let e=Number(t?.delay);if(!Number.isFinite(e)||e<=0)return"0s";let o=Number(t?.scheduled_at??t?.timestamp);if(!Number.isFinite(o)||o<=0)return this.formatLLMDuration(e);let i=o<1e11?o*1e3:o,r=Math.min(e,Math.max(0,(this.retryClock()-i)/1e3));return this.formatLLMDuration(r)}isLLMRetryEntryWaiting(t){let e=Number(t?.delay),o=Number(t?.scheduled_at??t?.timestamp);if(!Number.isFinite(e)||e<=0||!Number.isFinite(o))return!1;let i=o<1e11?o*1e3:o;return this.retryClock()r.session_id===e);return!(i&&(i.status==="completed"||i.status==="failed"||i.status==="cancelled"))}isItemRunning(t,e){if(!t||!t.data||!this.isCurrentSessionActive()||t.data.status==="success"||t.data.status==="failed"||t.data.status==="cancelled"||this.isReportStatusAction(t.data)||this.isReportStatusAction(t.data?.action_taken))return!1;if(t.data.status==="running")return!0;let o=this.consolidatedBlocks(),i=o.length>0?o[o.length-1]:null;if(i&&(i.id===e?.id||!e?.data?.isCompleted)){let r=this.getSortedStepEvents(e.data),a=r.length>0?r[r.length-1]:null;if(a&&(a.data?.trace_id===t.data?.trace_id||a===t))return!0}return!1}isBlockRunning(t){if(!t||!t.data||!this.isCurrentSessionActive())return!1;if(t.type==="checker")return t.data.isCompleted===!1;if(this.isReportStatusAction(t.data.action_taken)||t.data?.status==="completed"||t.data?.status==="failed"||t.data?.status==="cancelled")return!1;if(t.data.isCompleted===!1)return!0;let e=this.consolidatedBlocks();return(e.length>0?e[e.length-1]:null)?.id===t.id}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-agent-stream"]],decls:21,vars:11,consts:[["preImg",""],["preOverlay",""],["preImgTool",""],["preOverlayTool",""],["modalImg",""],["modalOverlay",""],[1,"agent-stream-container"],[1,"vscode-nav-header"],[1,"vscode-tab-container"],["title","Task Queue","aria-label","Task Queue",1,"vscode-tab-btn",3,"click"],[1,"material-symbols-outlined","tab-icon"],[1,"tab-label"],["title","Notes & Plans","aria-label","Notes & Plans",1,"vscode-tab-btn",3,"click"],[1,"vscode-dropdown-menu","task-dropdown"],[1,"vscode-dropdown-menu","notes-dropdown"],[1,"stream-floating-toolbar"],[1,"stream-logs-content"],["aria-live","polite",1,"session-loading-state"],[1,"empty-state"],[1,"logs-list-container"],[1,"image-modal-backdrop"],[1,"popover-title-header"],[1,"material-symbols-outlined"],[1,"dropdown-section-label"],[1,"material-symbols-outlined","icon-bolt"],[1,"dropdown-empty-state"],[1,"dropdown-item-card",3,"selected","running"],[1,"dropdown-section-label","history-label"],[1,"material-symbols-outlined","icon-history"],[1,"dropdown-history-list"],[1,"dropdown-item-card",3,"click"],[1,"item-header"],[1,"task-badge",3,"ngClass"],[1,"status-dot"],[1,"item-header-actions"],[1,"item-device",3,"title"],[1,"item-time-divider"],[1,"item-time"],["type","button","title","Stop this task","aria-label","Stop this task",1,"btn-stop-card"],["type","button","title","Delete this task","aria-label","Delete this task",1,"btn-delete-card"],[1,"item-goal",3,"title"],[1,"item-footer"],[1,"item-id",3,"title"],[1,"item-date"],[1,"material-symbols-outlined","device-icon"],[1,"device-name"],["type","button","title","Stop this task","aria-label","Stop this task",1,"btn-stop-card",3,"click"],["type","button","title","Delete this task","aria-label","Delete this task",1,"btn-delete-card",3,"click"],[1,"notes-selector-container"],[1,"dropdown-notes-content"],[1,"notes-selector-list"],[1,"note-selector-pill",3,"active"],[1,"note-selector-pill",3,"click"],[1,"material-symbols-outlined","pill-icon"],[1,"material-symbols-outlined","empty-icon"],[1,"note-title"],[1,"milestones-container"],[1,"other-lines-container"],[1,"milestone-block",3,"ngClass"],[1,"milestone-title-row"],[1,"material-symbols-outlined","milestone-status-icon","checked"],[1,"material-symbols-outlined","milestone-status-icon","progress"],[1,"material-symbols-outlined","milestone-status-icon","unchecked"],[1,"milestone-text"],[3,"bold-segment","code-segment"],[1,"milestone-substeps"],[1,"milestone-checks-container"],[1,"substep-row",3,"ngClass","padding-left"],[1,"substep-row",3,"ngClass"],[1,"material-symbols-outlined","check-icon","checked"],[1,"material-symbols-outlined","check-icon","progress"],[1,"material-symbols-outlined","check-icon","unchecked"],[1,"bullet"],[1,"line-text"],[1,"verification-card",3,"ngClass","margin-left"],[1,"verification-card",3,"ngClass"],[1,"card-header"],[1,"card-tag"],[1,"card-body"],[1,"verification-card","standalone",3,"ngClass","margin-left"],[1,"md-line",3,"ngClass","margin-left"],[1,"verification-card","standalone",3,"ngClass"],[1,"md-line",3,"ngClass"],[1,"model-info-chip",3,"ngClass","title"],[1,"btn-screen-recording",3,"click","title"],[1,"material-symbols-outlined","video-icon"],[1,"material-symbols-outlined","video-icon","spin-animation"],[1,"material-symbols-outlined","video-icon","play-icon"],[1,"material-symbols-outlined","video-icon","steps-icon"],[1,"material-symbols-outlined","video-icon","error-icon"],[1,"btn-text"],[1,"video-pulse-dot","red"],[1,"steps-count-badge"],[1,"material-symbols-outlined","model-icon"],[1,"model-label"],[1,"model-name"],[1,"material-symbols-outlined","spin-animation"],[1,"empty-icon"],["aria-live","polite",1,"phase-container","startup-phase"],[1,"phase-container"],[1,"outputter-synthesis-stream-card"],[1,"planning-loader-container"],[1,"phase-header","first-phase"],[1,"phase-worked-time"],["aria-label","Task startup progress",1,"phase-body"],[1,"block-item","type-stream"],[1,"step-body"],[1,"step-actions-container"],[1,"llm-retry-notes"],[1,"llm-retry-note"],[1,"stream-status-dot"],[1,"llm-retry-note-text"],[1,"llm-retry-status-line"],[1,"phase-header",3,"first-phase"],[1,"phase-body"],[1,"phase-header"],[1,"phase-token-usage",3,"title"],[1,"token-dot"],[1,"token-text"],[1,"block-item",3,"ngClass"],[1,"check-lane",3,"ngClass"],[1,"check-result",3,"ngClass"],[1,"check-header"],[1,"check-title"],[1,"check-subject",3,"title"],[1,"check-status"],[1,"check-items"],[1,"check-item"],[1,"check-index"],[1,"check-text"],[1,"check-method"],[1,"thinking-stream-details","native-thinking-details",3,"is-running","collapsed"],[1,"step-raw-thinking-message",3,"is-running"],[1,"step-android-action-card","task-report-card",3,"is-completed","is-failed","is-expanded"],[1,"step-android-action-card",3,"is-running","is-failed","is-expanded"],[1,"thinking-stream-details","native-thinking-details"],[1,"details-summary",3,"click"],[1,"details-content-wrapper"],[1,"details-content","stream-box"],[1,"stream-text",3,"innerHTML"],[1,"step-raw-thinking-message"],[1,"report-header"],[1,"report-content","stream-box"],[1,"step-android-action-card","task-report-card"],[1,"action-card-header",3,"click"],[1,"material-symbols-outlined","action-icon","report-icon"],[1,"action-title"],[1,"action-status-badge"],[1,"material-symbols-outlined","status-icon"],[1,"action-expand-icon","material-symbols-outlined"],[1,"action-card-body"],[1,"action-card-expanded-details"],[1,"action-detail-row"],[1,"detail-label"],[1,"detail-value"],[1,"action-card-expanded-details",3,"click"],[1,"expanded-grid"],[1,"grid-item","full-width"],[1,"expanded-screenshots-container"],[1,"grid-label"],[1,"grid-value","report-markdown-body",3,"innerHTML"],[1,"screenshot-column"],[1,"screenshot-label"],[1,"image-wrapper",3,"click"],[1,"img-relative-box"],["alt","Final Screen Screenshot",3,"src"],["alt","Post Screenshot",3,"src"],[1,"step-android-action-card"],[1,"material-symbols-outlined","action-icon"],[1,"action-status-badge","is-failed"],[1,"detail-value","coordinate-badge"],[1,"grid-item"],[1,"expanded-error-banner"],[1,"grid-value"],[1,"grid-value","code-pill"],["alt","Pre-Action Screenshot",3,"load","src"],[1,"overlay-layer"],["alt","Post-Action Screenshot",3,"src"],[1,"material-symbols-outlined","icon"],[1,"error-text"],[1,"llm-failure-stack"],[1,"step-generic-tool",3,"is-running","is-note-tool","is-video-tool","is-video-attention"],[1,"step-android-action-card","tool-action-card",3,"is-running","has-no-details","is-failed","is-expanded"],[1,"llm-retry-guidance"],[1,"step-android-action-card","tool-action-card","is-failed",3,"click"],[1,"action-card-header","llm-failure-header"],[1,"material-symbols-outlined","action-icon","is-failed"],[1,"action-status-badge","is-paused"],[1,"failure-retry-details"],[1,"llm-resume-row"],[1,"failure-retry-title"],[1,"failure-retry-row"],["type","button","title","Continue the paused task","aria-label","Continue the paused task",1,"btn-continue-task",3,"click"],[1,"material-symbols-outlined","continue-icon"],[1,"material-symbols-outlined","continue-arrow"],[1,"llm-resume-hint"],[1,"step-generic-tool"],[1,"tool-call-label"],[1,"tool-call-text"],["title","Click to view note content",1,"tool-key-pill"],["title","Click to view note content",1,"tool-key-pill",3,"click"],[1,"tool-key-pill",3,"click","title"],[1,"video-status-detail"],[1,"step-android-action-card","tool-action-card"],[1,"action-agent-badge",3,"is-outputter"],[1,"action-agent-badge"],[1,"detail-value","command-value"],[1,"grid-value","command-value"],[1,"check-verdict",3,"ngClass"],[1,"check-note"],[1,"material-symbols-outlined","verdict-icon"],[1,"verdict-body"],[1,"verdict-evidence"],[1,"verdict-suggestion"],[1,"synthesis-card-header"],[1,"header-left"],[1,"material-symbols-outlined","icon-sparkle"],[1,"main-title"],[1,"header-right"],["type","button",1,"btn-open-note",3,"click"],[1,"synthesis-card-preview"],[1,"parsed-note-container"],[1,"parsed-note-title"],[1,"parsed-milestones"],[1,"parsed-other-lines"],[1,"milestone-item",3,"class"],[1,"milestone-item"],[1,"other-line",3,"class"],[1,"other-line"],[1,"verify-badge",3,"assert"],[1,"finding-badge"],[1,"verify-badge"],[1,"planning-loader"],[1,"loader-text"],[1,"image-modal-backdrop",3,"click"],[1,"image-modal-content",3,"click"],["aria-label","Close image preview",1,"image-modal-close",3,"click"],["alt","Zoomed Screenshot",3,"load","src"]],template:function(e,o){e&1&&(l(0,"div",6)(1,"header",7)(2,"div",8)(3,"button",9),k("click",function(r){return o.toggleTaskDropdown(r)}),l(4,"span",10),u(5,"list_alt"),d(),l(6,"span",11),u(7,"Task Queue"),d()(),l(8,"button",12),k("click",function(r){return o.toggleNotesDropdown(r)}),l(9,"span",10),u(10,"description"),d(),l(11,"span",11),u(12,"Notes & Plans"),d()()(),M(13,aA,23,4,"div",13),M(14,$A,9,3,"div",14),d(),M(15,nN,17,28,"div",15),l(16,"main",16),M(17,oN,5,0,"div",17),M(18,iN,8,0,"div",18),M(19,d3,6,3,"div",19),d(),M(20,u3,10,1,"div",20),d()),e&2&&(g(3),w("active",o.isTaskDropdownOpen()),g(5),w("active",o.isNotesDropdownOpen()),g(5),b(o.isTaskDropdownOpen()?13:-1),g(),b(o.isNotesDropdownOpen()?14:-1),g(),b(o.consolidatedBlocks().length>0||o.showPlanningLoader()?15:-1),g(2),b(o.agentService.isSessionContentLoading()?17:-1),g(),b(!o.agentService.isSessionContentLoading()&&o.consolidatedBlocks().length===0&&!o.showPlanningLoader()&&o.startupWorkItems().length===0?18:-1),g(),b(!o.agentService.isSessionContentLoading()&&(o.phases().length>0||o.showPlanningLoader()||o.startupWorkItems().length>0)?19:-1),g(),b(o.selectedZoomImage()?20:-1))},dependencies:[No,ki,Qt,Ya,Ei],styles:['@charset "UTF-8";[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.agent-stream-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;height:100%;background:#f8f9fa;box-sizing:border-box;position:relative}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%]{position:relative;height:60px;background:#fff;border-bottom:1px solid rgba(0,0,0,.08);display:flex;align-items:center;justify-content:flex-end;padding:0 16px;box-sizing:border-box;-webkit-user-select:none;user-select:none;box-shadow:none;z-index:10}@media(min-width:1151px){.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%]{display:none!important}}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-container[_ngcontent-%COMP%]{display:flex;height:36px;box-sizing:border-box;background:#f1f5f9b8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:18px;padding:3px;gap:3px;align-items:center;flex-shrink:0;box-shadow:0 2px 8px -1px #0f172a0a,inset 0 1px 1px #fffc}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:30px;padding:0 14px;box-sizing:border-box;border:none;background:transparent;border-radius:15px;font-size:13px;font-weight:600;color:#64748b;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;text-decoration:none;cursor:pointer;position:relative;line-height:1;transition:all .2s cubic-bezier(.4,0,.2,1);outline:none;white-space:nowrap}@media(max-width:1150px){.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%]{padding:0 9px;gap:0}}@media(max-width:1150px){.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%]{display:none!important}}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{font-size:16px;color:inherit;transition:color .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%] .badge-count[_ngcontent-%COMP%]{background:#2563eb;color:#fff;font-size:10px;font-weight:700;padding:1px 6px;border-radius:10px;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%] .dropdown-chevron[_ngcontent-%COMP%]{font-size:16px;color:#94a3b8;margin-left:2px;transition:transform .2s ease,color .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%] .dropdown-chevron.open[_ngcontent-%COMP%]{transform:rotate(180deg);color:#1e293b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn[_ngcontent-%COMP%]:hover:not(.active){color:#0f172a;background:#fff9}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn.active[_ngcontent-%COMP%]{background:#fff;color:#2563eb;box-shadow:0 2px 8px #0000000f,0 1px 2px #00000005}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn.active[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn.video-replay-tab-btn[_ngcontent-%COMP%] .video-indicator-dot[_ngcontent-%COMP%]{width:7px;height:7px;border-radius:50%;background:#10b981;box-shadow:0 0 6px #10b981;margin-left:-2px;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-tab-btn.video-replay-tab-btn.has-video[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{color:#0284c7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%]{margin-left:auto;display:flex;align-items:center}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-stop-task[_ngcontent-%COMP%]{padding:5px 10px;background:#2563eb;border:1px solid #2563eb;border-radius:16px;font-size:13px;font-weight:600;color:#fff;cursor:pointer;display:flex;align-items:center;gap:4px;transition:all .2s;box-shadow:0 2px 4px #2563eb33}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-stop-task[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%]:hover, .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-stop-task[_ngcontent-%COMP%]:hover{background:#1d4ed8;border-color:#1d4ed8;box-shadow:0 4px 8px #2563eb4d}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-stop-task[_ngcontent-%COMP%]{background:#ef4444;border-color:#ef4444;box-shadow:0 2px 4px #ef444433}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .header-right-actions[_ngcontent-%COMP%] .btn-stop-task[_ngcontent-%COMP%]:hover{background:#dc2626;border-color:#dc2626;box-shadow:0 4px 8px #ef44444d}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%]{position:absolute;top:48px;right:12px;left:auto;width:360px;max-width:calc(100vw - 24px);max-height:480px;background:#fff;border:1px solid #cbd5e1;border-radius:12px;box-shadow:0 12px 32px #00000024,0 4px 12px #0000000d;z-index:100;overflow-y:auto;padding:14px;display:flex;flex-direction:column;gap:10px;animation:_ngcontent-%COMP%_dropdownFadeIn .15s cubic-bezier(.16,1,.3,1)}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .popover-title-header[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;padding-bottom:8px;border-bottom:1px solid #e2e8f0;font-size:14px;font-weight:700;color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .popover-title-header[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px;color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%]{right:12px;left:auto;width:380px;max-height:520px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px;margin-bottom:4px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border:1px solid #e2e8f0;background:#fff;border-radius:16px;font-size:11.5px;font-weight:500;color:#475569;cursor:pointer;transition:all .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%] .pill-icon[_ngcontent-%COMP%]{font-size:14px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%]:hover{background:#f8fafc;border-color:#cbd5e1;color:#1e293b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill.active[_ngcontent-%COMP%]{background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8;font-weight:600}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px;padding:4px 2px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .note-title[_ngcontent-%COMP%]{font-size:13.5px;font-weight:700;color:#1f1f1f;margin:0 0 4px;font-family:Google Sans,Google Sans Text,Inter,sans-serif}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .code-segment[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;background-color:#f1f3f4;color:#1967d2;padding:1px 5px;border-radius:4px;font-size:12px;margin:0 2px;word-break:break-all}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestones-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%]{display:flex;flex-direction:column}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block.checked[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.checked[_ngcontent-%COMP%]{opacity:.7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block.checked[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%]{color:#a1a1aa}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon[_ngcontent-%COMP%]{font-size:18px;-webkit-user-select:none;user-select:none;margin-top:2px;font-variation-settings:"FILL" 1,"wght" 400,"GRAD" 0,"opsz" 24}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.checked[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.progress[_ngcontent-%COMP%]{color:#1a73e8}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.unchecked[_ngcontent-%COMP%]{color:#cbd5e1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,sans-serif;color:#1f1f1f;font-size:13px;line-height:1.5;word-break:break-word;flex:1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%]{margin-top:4px;display:flex;flex-direction:column;gap:5px;padding-left:26px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:8px;font-family:Google Sans,Google Sans Text,Inter,sans-serif;color:#1f1f1f;font-size:12.5px;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.checked[_ngcontent-%COMP%]{color:#a1a1aa}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.checked[_ngcontent-%COMP%] .check-icon.checked[_ngcontent-%COMP%]{opacity:.7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.verify[_ngcontent-%COMP%]{color:#334155;gap:8px;align-items:flex-start}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.verify[_ngcontent-%COMP%] .verify-badge[_ngcontent-%COMP%]{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-family:Google Sans Text,Inter,-apple-system,sans-serif;font-size:10.5px;font-weight:550;line-height:1.2;letter-spacing:.2px;text-transform:lowercase;color:#475569;background:#f1f5f9;border:1px solid #e2e8f0;border-radius:4px;padding:1.5px 6px;margin-top:1.5px;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.verify[_ngcontent-%COMP%] .verify-badge.assert[_ngcontent-%COMP%]{color:#0369a1;background:#f0f9ff;border-color:#e0f2fe}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.verify[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{color:#334155;font-size:12.5px;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.finding[_ngcontent-%COMP%]{color:#92400e;gap:8px;align-items:flex-start}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.finding[_ngcontent-%COMP%] .finding-badge[_ngcontent-%COMP%]{flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;font-family:Google Sans Text,Inter,-apple-system,sans-serif;font-size:10.5px;font-weight:550;line-height:1.2;letter-spacing:.2px;text-transform:lowercase;color:#b45309;background:#fef3c7;border:1px solid #fde68a;border-radius:4px;padding:1.5px 6px;margin-top:1.5px;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.finding[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{color:#92400e;font-size:12.5px;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{font-size:16px;-webkit-user-select:none;user-select:none;margin-top:2px;font-variation-settings:"FILL" 1,"wght" 400,"GRAD" 0,"opsz" 24}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.checked[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.progress[_ngcontent-%COMP%]{color:#1a73e8}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.unchecked[_ngcontent-%COMP%]{color:#cbd5e1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .bullet[_ngcontent-%COMP%]{color:#9ca3af;font-weight:700;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{word-break:break-word;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-checks-container[_ngcontent-%COMP%]{margin-top:6px;padding-left:26px;display:flex;flex-direction:column;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:7px 10px;display:flex;flex-direction:column;gap:3px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card.standalone[_ngcontent-%COMP%]{margin-top:6px;margin-bottom:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%]{display:flex;align-items:center}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag[_ngcontent-%COMP%]{font-family:Google Sans Text,Inter,-apple-system,sans-serif;font-size:9.5px;font-weight:600;line-height:1;letter-spacing:.6px;text-transform:uppercase;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag.assert[_ngcontent-%COMP%]{color:#0284c7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%]{font-family:inherit;font-size:12px;line-height:1.5;color:#334155;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .bold-segment[_ngcontent-%COMP%]{font-weight:600;color:#1e293b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .code-segment[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;background:#f1f5f9;color:#0f172a;padding:1px 5px;border-radius:4px;border:1px solid #e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%]{background:#fffbeb;border-color:#fef3c7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag[_ngcontent-%COMP%]{color:#b45309}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%]{color:#92400e}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px;margin-top:6px;padding-top:6px;border-top:1px dashed #e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:6px;font-size:12.5px;color:#334155;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu.notes-dropdown[_ngcontent-%COMP%] .dropdown-notes-content[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{word-break:break-word;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%]::-webkit-scrollbar{width:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding-bottom:8px;border-bottom:1px solid #e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-header[_ngcontent-%COMP%] .dropdown-title[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;font-size:13px;font-weight:700;color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-header[_ngcontent-%COMP%] .dropdown-title[_ngcontent-%COMP%] .icon-main[_ngcontent-%COMP%]{font-size:18px;color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-section-label[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.5px;padding:4px 2px 2px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-section-label.history-label[_ngcontent-%COMP%]{margin-top:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-section-label[_ngcontent-%COMP%] .icon-bolt[_ngcontent-%COMP%]{font-size:15px;color:#d97706}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-section-label[_ngcontent-%COMP%] .icon-history[_ngcontent-%COMP%]{font-size:15px;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-empty-state[_ngcontent-%COMP%]{padding:20px 14px;text-align:center;font-size:12px;color:#94a3b8;background:#f8fafc;border:1px dashed #e2e8f0;border-radius:6px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%]{font-size:22px;color:#94a3b8}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-history-list[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]{padding:10px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;cursor:pointer;transition:all .15s ease;display:flex;flex-direction:column;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover{background:#f1f5f9;border-color:#cbd5e1;transform:translateY(-1px)}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%]:has(.btn-delete-card) .item-time[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%]:has(.btn-delete-card) .item-time-divider[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%]:has(.btn-stop-card) .item-time[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%]:has(.btn-stop-card) .item-time-divider[_ngcontent-%COMP%]{opacity:0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%]:hover .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]{opacity:1;pointer-events:auto}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card.selected[_ngcontent-%COMP%]{background:#eff6ff;border-color:#93c5fd;box-shadow:0 0 0 1px #2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card.selected[_ngcontent-%COMP%] .item-goal[_ngcontent-%COMP%]{color:#1e3a8a;font-weight:600}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card.running[_ngcontent-%COMP%]{border-left:3.5px solid #2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:8px;min-width:0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%]{flex-shrink:0;display:inline-flex;align-items:center;font-size:10.5px;font-weight:700;padding:2px 5px;border-radius:4px;letter-spacing:.05em;border:1px solid #e2e8f0;background:#fafafa;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%]:before{content:"";width:4px;height:4px;border-radius:50%;display:inline-block;margin-right:4px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.running[_ngcontent-%COMP%]{background:#fafafa;color:#1f1f1f;border-color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.running[_ngcontent-%COMP%]:before{background-color:#1f1f1f;animation:_ngcontent-%COMP%_pulse-black-dot-small 1.5s infinite ease-in-out}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.paused[_ngcontent-%COMP%]{background:#fff7ed;color:#c2410c;border-color:#fdba74}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.paused[_ngcontent-%COMP%]:before{background-color:#f97316}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.completed[_ngcontent-%COMP%]{background:#fafafa;color:#1f1f1f;border-color:#e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.completed[_ngcontent-%COMP%]:before{background-color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.pending[_ngcontent-%COMP%]{background:#fafafa;color:#64748b;border-color:#e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.pending[_ngcontent-%COMP%]:before{background-color:#94a3b8}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.failed[_ngcontent-%COMP%]{background:#fafafa;color:#ef4444;border-color:#fee2e2}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.failed[_ngcontent-%COMP%]:before{background-color:#ef4444}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.cancelled[_ngcontent-%COMP%]{background:#fafafa;color:#d97706;border-color:#fef3c7}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge.cancelled[_ngcontent-%COMP%]:before{background-color:#f59e0b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%] .status-dot[_ngcontent-%COMP%]{display:none}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:4px;position:relative;min-width:0;flex-shrink:1;justify-content:flex-end}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-device[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:2.5px;min-width:0;flex-shrink:1;font-size:10.5px;color:#64748b;line-height:1;cursor:default;transition:color .15s ease,opacity .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-device[_ngcontent-%COMP%] .device-icon[_ngcontent-%COMP%]{font-size:12px;width:12px;height:12px;line-height:12px;color:#64748b;opacity:.75;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-device[_ngcontent-%COMP%] .device-name[_ngcontent-%COMP%]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:inherit;letter-spacing:-.15px}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-device[_ngcontent-%COMP%]:hover{color:#334155}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-device[_ngcontent-%COMP%]:hover .device-icon[_ngcontent-%COMP%]{color:#334155;opacity:1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-time-divider[_ngcontent-%COMP%]{font-size:10px;color:#94a3b8;line-height:1;-webkit-user-select:none;user-select:none;flex-shrink:0;transition:opacity .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .item-time[_ngcontent-%COMP%]{font-size:11px;color:#64748b;white-space:nowrap;flex-shrink:0;line-height:1;transition:opacity .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%]{position:absolute;right:0;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;border-radius:3px;color:#94a3b8;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:all .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:13px;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%]:hover{color:#ef4444;background:#fee2e2}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]{position:absolute;right:0;top:50%;transform:translateY(-50%);width:18px;height:18px;padding:0;border:none;background:transparent;border-radius:3px;color:#ef4444;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:all .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-header[_ngcontent-%COMP%] .item-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]:hover{color:#dc2626;background:#fee2e2;transform:translateY(-50%) scale(1.1)}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-goal[_ngcontent-%COMP%]{font-size:12.5px;color:#334155;line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-footer[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:10.5px;color:#94a3b8}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-footer[_ngcontent-%COMP%] .item-id[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:10.5px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1;transition:color .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-footer[_ngcontent-%COMP%] .item-id[_ngcontent-%COMP%]:hover{color:#475569}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-item-card[_ngcontent-%COMP%] .item-footer[_ngcontent-%COMP%] .item-date[_ngcontent-%COMP%]{flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;cursor:pointer;font-size:13px;color:#334155;transition:background .15s ease}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item[_ngcontent-%COMP%] .note-icon[_ngcontent-%COMP%]{font-size:18px;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item[_ngcontent-%COMP%] .note-key-text[_ngcontent-%COMP%]{flex:1;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{font-size:18px;color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item[_ngcontent-%COMP%]:hover{background:#f1f5f9;color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item.selected[_ngcontent-%COMP%]{background:#eff6ff;color:#2563eb;font-weight:600}.agent-stream-container[_ngcontent-%COMP%] .vscode-nav-header[_ngcontent-%COMP%] .vscode-dropdown-menu[_ngcontent-%COMP%] .dropdown-note-item.selected[_ngcontent-%COMP%] .note-icon[_ngcontent-%COMP%]{color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%]{position:absolute;top:12px;right:20px;height:36px;display:inline-flex;align-items:center;gap:8px;z-index:40;pointer-events:auto}@media(max-width:1150px){.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%]{top:72px}}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .model-info-chip[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 11px;box-sizing:border-box;background:#ffffffb8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:15px;cursor:default;-webkit-user-select:none;user-select:none;box-shadow:0 2px 6px -1px #0f172a0d,inset 0 1px 1px #ffffffe6;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:all .2s cubic-bezier(.4,0,.2,1)}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .model-info-chip[_ngcontent-%COMP%] .model-icon[_ngcontent-%COMP%]{font-size:15px;width:15px;height:15px;display:inline-flex;align-items:center;justify-content:center;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .model-info-chip[_ngcontent-%COMP%] .model-label[_ngcontent-%COMP%]{font-size:12px;font-weight:500;color:#64748b;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .model-info-chip[_ngcontent-%COMP%] .model-name[_ngcontent-%COMP%]{font-size:12px;font-weight:700;color:#1e293b;letter-spacing:.1px;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;height:30px;padding:0 12px;box-sizing:border-box;background:#ffffffb8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:15px;box-shadow:0 2px 6px -1px #0f172a0d,inset 0 1px 1px #ffffffe6;cursor:pointer;transition:all .2s cubic-bezier(.16,1,.3,1);-webkit-user-select:none;user-select:none;outline:none;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%] .video-icon[_ngcontent-%COMP%]{font-size:15px;width:15px;height:15px;display:inline-flex;align-items:center;justify-content:center;color:#64748b;transition:transform .2s ease,color .2s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{font-size:12px;font-weight:600;color:#475569;letter-spacing:.1px;line-height:1;transition:color .15s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%] .video-pulse-dot[_ngcontent-%COMP%]{width:6px;height:6px;border-radius:50%;background:#ef4444;box-shadow:0 0 6px #ef4444;animation:pulseRed 1.5s infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.recording[_ngcontent-%COMP%]{background:#fef2f2e6;border-color:#f8717199;box-shadow:0 2px 6px -1px #dc262626,inset 0 1px 1px #ffffffe6}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.recording[_ngcontent-%COMP%] .video-icon[_ngcontent-%COMP%]{color:#dc2626}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.recording[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#b91c1c}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.processing[_ngcontent-%COMP%] .video-icon[_ngcontent-%COMP%]{color:#2563eb;animation:_ngcontent-%COMP%_spin 1s linear infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%]:hover:not(.active){background:#ffffffe0;border-color:#cbd5e1f2;box-shadow:0 4px 12px -2px #0f172a14,inset 0 1px 1px #fff;color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%]:hover:not(.active) .video-icon[_ngcontent-%COMP%]{color:#1a73e8;transform:scale(1.08)}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%]:hover:not(.active) .btn-text[_ngcontent-%COMP%]{color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%]:active{transform:translateY(0);box-shadow:0 1px 3px #0f172a14}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.has-steps[_ngcontent-%COMP%]{background:#f0fdfae6;border-color:#5eead4b3;box-shadow:0 2px 6px -1px #0d94881f,inset 0 1px 1px #ffffffe6}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.has-steps[_ngcontent-%COMP%] .steps-icon[_ngcontent-%COMP%]{color:#0d9488}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.has-steps[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#0f766e}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.has-steps[_ngcontent-%COMP%]:hover:not(.active){background:#ccfbf1f2;border-color:#2dd4bfd9}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.has-steps[_ngcontent-%COMP%]:hover:not(.active) .steps-icon[_ngcontent-%COMP%]{color:#0f766e}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.failed[_ngcontent-%COMP%]{background:#fef2f2cc;border-color:#fecacae6}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.failed[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%]{color:#ef4444}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.failed[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#dc2626}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording[_ngcontent-%COMP%] .steps-count-badge[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;padding:1px 5px;border-radius:10px;background:#0d9488;color:#fff;font-size:10px;font-weight:700;line-height:1;margin-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.active[_ngcontent-%COMP%]{background:#1a73e8f2;border-color:#1a73e8f2;box-shadow:0 2px 8px #1a73e859}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.active[_ngcontent-%COMP%] .video-icon[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.active[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{color:#fff}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.active[_ngcontent-%COMP%] .video-pulse-dot[_ngcontent-%COMP%]{background:#fff;box-shadow:0 0 6px #ffffffe6}.agent-stream-container[_ngcontent-%COMP%] .stream-floating-toolbar[_ngcontent-%COMP%] .btn-screen-recording.active[_ngcontent-%COMP%] .steps-count-badge[_ngcontent-%COMP%]{background:#fff;color:#1a73e8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]{flex:1;overflow-y:auto;padding:72px 24px 100px;position:relative}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .session-loading-state[_ngcontent-%COMP%]{min-height:180px;display:flex;align-items:center;justify-content:center;gap:9px;color:#64748b;font-size:13px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .session-loading-state[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]{display:flex;flex-direction:column}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .floating-model-badge[_ngcontent-%COMP%]{position:sticky;top:0;float:right;margin-top:-2px;margin-right:4px;display:inline-flex;align-items:center;gap:4px;padding:3px 9px;background:#ffffffeb;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(99,102,241,.2);border-radius:12px;box-shadow:0 1px 4px #6366f114,0 1px 2px #0000000a;z-index:35;cursor:default;transition:all .2s ease;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .floating-model-badge[_ngcontent-%COMP%]:hover{background:#fff;border-color:#6366f166;box-shadow:0 2px 8px #6366f126;transform:translateY(-1px)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .floating-model-badge[_ngcontent-%COMP%]:hover .model-icon[_ngcontent-%COMP%]{color:#4f46e5;transform:rotate(15deg) scale(1.05)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .floating-model-badge[_ngcontent-%COMP%] .model-icon[_ngcontent-%COMP%]{font-size:13px;color:#6366f1;transition:all .3s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .floating-model-badge[_ngcontent-%COMP%] .model-name[_ngcontent-%COMP%]{font-size:11px;font-weight:600;color:#334155;letter-spacing:normal;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar{width:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#00000026;border-radius:10px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#00000040}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%]{flex:1 1 auto;min-height:100%;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;box-sizing:border-box;padding:30px;margin:auto 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%]{margin-bottom:12px;opacity:.8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:40px;color:#94a3b8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{margin:0 0 6px;font-size:12px;font-weight:600;color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0;font-size:12px;color:#1f1f1f;max-width:320px;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list-container[_ngcontent-%COMP%]{display:flex;flex-direction:column}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list-container[_ngcontent-%COMP%] .planning-loader-container[_ngcontent-%COMP%]{display:flex;align-items:center;margin-top:10px;margin-bottom:10px;padding-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list-container[_ngcontent-%COMP%] .planning-loader-container[_ngcontent-%COMP%] .planning-loader[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:8px;color:#475569;font-size:13px;font-weight:500;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list-container[_ngcontent-%COMP%] .planning-loader-container[_ngcontent-%COMP%] .planning-loader[_ngcontent-%COMP%] .spin-animation[_ngcontent-%COMP%]{font-size:16px;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;animation:_ngcontent-%COMP%_spin-loader 1s linear infinite;color:#2563eb;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list-container[_ngcontent-%COMP%] .planning-loader-container[_ngcontent-%COMP%] .planning-loader[_ngcontent-%COMP%] .loader-text[_ngcontent-%COMP%]{color:#475569;font-size:13px;font-weight:500;line-height:1.4}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-container[_ngcontent-%COMP%]{display:flex;flex-direction:column}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header[_ngcontent-%COMP%]{font-size:12px;font-weight:400;line-height:1.5;min-height:20px;box-sizing:border-box;color:#71717a;margin-top:16px;margin-bottom:8px;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header.first-phase[_ngcontent-%COMP%]{margin-top:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header[_ngcontent-%COMP%] .phase-worked-time[_ngcontent-%COMP%]{color:#71717a;font-weight:400}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header[_ngcontent-%COMP%] .phase-token-usage[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;color:#8e8e93;cursor:default}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header[_ngcontent-%COMP%] .phase-token-usage[_ngcontent-%COMP%] .token-dot[_ngcontent-%COMP%]{color:#a1a1aa;font-weight:700}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-header[_ngcontent-%COMP%] .phase-token-usage[_ngcontent-%COMP%] .token-text[_ngcontent-%COMP%]{color:#71717a;font-variant-numeric:tabular-nums}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px;padding-left:12px;border-left:1px solid rgba(0,0,0,.04);margin-left:6px;position:relative;z-index:1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]{border-radius:8px;padding:0;overflow:visible;box-shadow:0 1px 3px #00000005;border:1px solid transparent;background:transparent;display:flex;flex-direction:column}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%]{display:grid;grid-template-rows:1fr;transition:grid-template-rows .3s cubic-bezier(.4,0,.2,1);overflow:hidden}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%] .details-content[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%] .details-content[_ngcontent-%COMP%]{min-height:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%]{border-color:transparent;background:transparent;box-shadow:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%]:not(.collapsed), .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%]:not(.collapsed){background:transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#71717a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after{color:#d4d4d8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover{color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details.is-running[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details.is-running[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{background:linear-gradient(90deg,#71717a,#2563eb,#7c3aed,#0284c7,#71717a);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:_ngcontent-%COMP%_subtle-color-flow 2.8s linear infinite;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details.is-running[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details.is-running[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#d4d4d8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.native-thinking-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%]{color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%]{border-color:#dbeafe;background:#f8fafc}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%]:not(.collapsed), .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%]:not(.collapsed){background:#f8fafc}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#1e40af}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after{color:#93c5fd}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover{color:#1e3a8a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#93c5fd}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.raw-thinking-details[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#2563eb;animation:_ngcontent-%COMP%_pulse-stream-blue 1.5s infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed), .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed){box-shadow:0 1px 3px #00000005}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed) .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed) .details-summary[_ngcontent-%COMP%]{margin-bottom:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed) .details-summary[_ngcontent-%COMP%]:after, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%]:not(.collapsed) .details-summary[_ngcontent-%COMP%]:after{transform:rotate(90deg)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{padding:0;margin-bottom:0;font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;font-weight:400;font-size:12px;line-height:1.5;min-height:20px;box-sizing:border-box;letter-spacing:.5px;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:8px;outline:none;list-style:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]::-webkit-details-marker, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]::-webkit-details-marker{display:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:after{content:"\\276f";display:inline-block;font-size:9px;margin-left:6px;transition:transform .2s cubic-bezier(.4,0,.2,1),color .2s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover .stream-status-dot[_ngcontent-%COMP%]{transform:scale(1.25);background:#71717a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover:after, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover:after{transform:translate(3px);color:#71717a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;display:inline-block;transition:transform .2s cubic-bezier(.4,0,.2,1),background-color .2s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]{max-height:250px;overflow-y:auto;background:transparent;padding:4px 0;box-sizing:border-box}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar{width:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-track, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#00000014;border-radius:999px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#00000029}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%]{font-family:inherit;font-size:12.5px;line-height:1.65;color:#1f1f1f;white-space:normal;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#111827}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] div[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] div[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h1[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h6[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h1[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h6[_ngcontent-%COMP%]{font-size:12.5px;font-weight:600;color:#1f1f1f;margin-top:8px;margin-bottom:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;background:#f4f4f5;color:#b91c1c;padding:2px 6px;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:4px 0;padding-left:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0;padding-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%]{height:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.collapsed[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.collapsed[_ngcontent-%COMP%] .details-content-wrapper[_ngcontent-%COMP%]{grid-template-rows:0fr;display:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-details.collapsed[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-details.collapsed[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{margin-bottom:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%]{background:transparent;border:1px solid transparent;border-radius:8px;padding:0;margin-bottom:0;box-shadow:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;font-weight:400;font-size:12px;line-height:1.5;min-height:20px;box-sizing:border-box;letter-spacing:.5px;color:#71717a;margin-bottom:8px;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;display:inline-block;background:#d4d4d8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#71717a;animation:_ngcontent-%COMP%_pulse-stream-grey 1.5s infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]{max-height:250px;overflow-y:auto;background:transparent;padding:4px 0;box-sizing:border-box}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar{width:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-track, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#00000014;border-radius:999px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover, .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-box[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#00000029}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%]{font-family:inherit;font-size:12.5px;line-height:1.65;color:#1f1f1f;white-space:normal;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#111827}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] div[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] div[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h1[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h1[_ngcontent-%COMP%]{font-size:14px;font-weight:700;color:#0f172a;margin-top:12px;margin-bottom:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{font-size:13.5px;font-weight:700;color:#0f172a;margin-top:10px;margin-bottom:5px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#1e293b;margin-top:8px;margin-bottom:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h6[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h4[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] h6[_ngcontent-%COMP%]{font-size:12.5px;font-weight:600;color:#334155;margin-top:6px;margin-bottom:3px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] em[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] em[_ngcontent-%COMP%]{font-style:italic;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] blockquote[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] blockquote[_ngcontent-%COMP%]{margin:6px 0;padding:4px 10px;border-left:3px solid #cbd5e1;color:#475569;background:#f1f5f980;border-radius:0 4px 4px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .code-block[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .code-block[_ngcontent-%COMP%]{margin:6px 0;padding:8px 12px;background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;overflow-x:auto;font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11.5px;line-height:1.5;color:#1e293b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-hr[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-hr[_ngcontent-%COMP%]{border:none;border-top:1px solid #e2e8f0;margin:10px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;background:#f4f4f5;color:#b91c1c;padding:2px 6px;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:4px 0;padding-left:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0;padding-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li.md-verify-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li.md-verify-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li.md-verify-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li.md-verify-item[_ngcontent-%COMP%]{color:#334155}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li.md-finding-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li.md-finding-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li.md-finding-item[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li.md-finding-item[_ngcontent-%COMP%]{color:#92400e}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .thinking-stream-flat-card[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%]{height:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%]{background:#f5f3ff;border:1px solid #e9d5ff;border-radius:8px;padding:12px 16px;margin-bottom:8px;box-shadow:0 1px 3px #00000003}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,Liberation Sans,sans-serif;font-weight:700;font-size:12px;line-height:1.5;letter-spacing:.5px;color:#6b21a8;margin-bottom:8px;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;display:inline-block;background:#c084fc}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#a855f7;animation:_ngcontent-%COMP%_pulse-stream-purple 1.5s infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%]{font-size:12px;line-height:1.6;color:#27272a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .stream-text[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11.5px;white-space:pre-wrap;word-break:break-all;color:#5b21b6;background:#ffffff80;padding:8px;border-radius:6px;border:1px dashed rgba(168,85,247,.2)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;font-weight:700;font-size:11.5px;padding:4px 10px;border-radius:6px;margin-bottom:10px;border:1px solid transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge.success[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge.success[_ngcontent-%COMP%]{background:#ecfdf5;color:#065f46;border-color:#a7f3d0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge.failed[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-result-badge.failed[_ngcontent-%COMP%]{background:#fef2f2;color:#991b1b;border-color:#fca5a5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .llm-stream-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-reason[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .checker-stream-flat-card[_ngcontent-%COMP%] .checker-reason[_ngcontent-%COMP%]{font-size:12px;line-height:1.6;color:#3f3f46;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%]{background:transparent;border:1px solid transparent;border-radius:8px;padding:0;margin-bottom:0;box-shadow:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message.is-running[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%]{background:linear-gradient(90deg,#71717a,#2563eb,#7c3aed,#0284c7,#71717a);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:_ngcontent-%COMP%_subtle-color-flow 2.8s linear infinite;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message.is-running[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;font-weight:400;font-size:12px;line-height:1.5;min-height:20px;box-sizing:border-box;letter-spacing:.5px;color:#71717a;margin-bottom:8px;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;display:inline-block;background:#d4d4d8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#111827}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] div[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h1[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h2[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h3[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h4[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h5[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] h6[_ngcontent-%COMP%]{font-size:12.5px;font-weight:600;color:#1f1f1f;margin-top:8px;margin-bottom:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;background:#f4f4f5;color:#b91c1c;padding:2px 6px;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:4px 0;padding-left:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{font-size:12.5px;line-height:1.65;color:#1f1f1f;margin:4px 0;padding-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-raw-thinking-message[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%]{height:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%]{background:#f4fbf7;border:1px solid #d1fae5;border-radius:8px;padding:12px 16px;margin-bottom:8px;box-shadow:0 1px 3px #00000003}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-header[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,Liberation Sans,sans-serif;font-weight:700;font-size:12px;line-height:1.5;letter-spacing:.5px;color:#065f46;margin-bottom:8px;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%]{font-size:12px;line-height:1.6;color:#27272a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#09090b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .inline-code[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;background:#e6f4ea;color:#137333;padding:2px 6px;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:2px 0;padding-left:20px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{margin-bottom:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-action-summary-card[_ngcontent-%COMP%] .report-content[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%]{height:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px;margin-top:0;margin-bottom:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px;padding:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%]{min-height:25px;box-sizing:border-box;display:flex;align-items:center;gap:8px;color:#71717a;font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;font-size:12px;line-height:1.5;letter-spacing:.2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;background:#d4d4d8;flex:0 0 auto}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .llm-retry-note-text[_ngcontent-%COMP%]{color:#71717a;display:flex;flex-direction:column;min-width:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .llm-retry-note-text[_ngcontent-%COMP%] .llm-retry-status-line[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .llm-retry-note-text[_ngcontent-%COMP%] .llm-retry-guidance[_ngcontent-%COMP%]{display:block}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-retry-notes[_ngcontent-%COMP%] .llm-retry-note[_ngcontent-%COMP%] .llm-retry-note-text[_ngcontent-%COMP%] .llm-retry-guidance[_ngcontent-%COMP%]{color:#a1a1aa}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%]{border:1px solid transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-video-attention[_ngcontent-%COMP%]:not(.is-running) .tool-call-label[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#f59e0b;box-shadow:none;animation:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-llm-error[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%]{display:flex;align-items:flex-start;color:#71717a;line-height:1.5;-webkit-user-select:text;user-select:text}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-llm-error[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%]{font-size:14px;color:#94a3b8;margin-top:1px;margin-left:-3px;margin-right:-3px;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-llm-error[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-call-text[_ngcontent-%COMP%]{display:block;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-llm-error[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .error-title-text[_ngcontent-%COMP%]{font-weight:500;color:#71717a;margin-right:4px;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-llm-error[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .error-message-text[_ngcontent-%COMP%]{color:#71717a;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-running[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool.is-running[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-call-text[_ngcontent-%COMP%] > span[_ngcontent-%COMP%]:first-child{background:linear-gradient(90deg,#71717a,#2563eb,#7c3aed,#0284c7,#71717a);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:_ngcontent-%COMP%_subtle-color-flow 2.8s linear infinite;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%]{font-size:12px;font-weight:400;color:#71717a;font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;letter-spacing:.5px;line-height:1.5;min-height:22px;box-sizing:border-box;display:inline-flex;align-items:center;gap:8px;-webkit-user-select:none;user-select:none;padding:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:not(.tool-key-pill){color:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;background:#d4d4d8;display:inline-block;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#3b82f6;box-shadow:0 0 #3b82f673;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .is-failed-dot[_ngcontent-%COMP%]{font-size:13px;color:#94a3b8;display:inline-flex;align-items:center;justify-content:center;width:8px;height:8px;flex-shrink:0;line-height:1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-call-text[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:2px;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-key-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4px;padding:2px 6px;border:none;background:transparent;font-size:12px;font-weight:500;color:#1a73e8;cursor:pointer;border-radius:4px;transition:all .2s ease;vertical-align:middle;text-decoration:none!important}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-key-pill[_ngcontent-%COMP%] .pill-icon[_ngcontent-%COMP%]{font-size:14px;color:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .tool-key-pill[_ngcontent-%COMP%]:hover{color:#1557b0;background:#1a73e814}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-generic-tool[_ngcontent-%COMP%] .tool-call-label[_ngcontent-%COMP%] .video-status-detail[_ngcontent-%COMP%]{color:inherit;margin-left:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%]{background:#fef2f2;border:1px solid #fecaca;border-radius:10px;padding:10px 12px;margin-top:0;margin-bottom:0;margin-left:-19px;box-shadow:0 4px 12px #ef444408;width:fit-content;min-width:260px;max-width:480px;display:flex;flex-direction:column;gap:8px;transition:all .2s ease;position:relative}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%]:hover{border-color:#fca5a5;box-shadow:0 4px 16px #ef444414}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;border-bottom:1px solid #fee2e2;padding-bottom:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-header[_ngcontent-%COMP%] .error-icon[_ngcontent-%COMP%]{font-size:18px;color:#ef4444}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-header[_ngcontent-%COMP%] .error-title[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif;font-weight:700;font-size:12px;color:#991b1b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-body[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-body[_ngcontent-%COMP%] .error-detail-row[_ngcontent-%COMP%]{display:flex;font-size:12px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-llm-error-card[_ngcontent-%COMP%] .error-card-body[_ngcontent-%COMP%] .error-detail-row[_ngcontent-%COMP%] .detail-value[_ngcontent-%COMP%]{color:#b91c1c;font-weight:500;word-break:break-word;line-height:1.4}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%]{width:380px;max-width:100%;margin-left:-19px;display:flex;flex-direction:column;align-items:flex-start;gap:10px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%]{width:100%;margin-left:0;box-sizing:border-box}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .llm-resume-row[_ngcontent-%COMP%]{min-height:36px;display:flex;align-items:center;gap:10px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]{min-height:36px;padding:7px 12px 7px 10px;display:inline-flex;align-items:center;justify-content:center;gap:7px;box-sizing:border-box;border:1px solid #1d4ed8;border-radius:9px;background:#2563eb;color:#fff;font-family:inherit;font-size:12px;font-weight:700;line-height:1;cursor:pointer;box-shadow:0 2px 7px #2563eb38;transition:background .18s ease,border-color .18s ease,box-shadow .18s ease,transform .18s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%] .continue-icon[_ngcontent-%COMP%]{width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;margin:-2px 0 -2px -2px;border-radius:50%;background:#ffffff29;font-size:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%] .continue-arrow[_ngcontent-%COMP%]{font-size:15px;opacity:.72;transition:transform .18s ease,opacity .18s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]:hover{background:#1d4ed8;border-color:#1e40af;box-shadow:0 4px 11px #2563eb47;transform:translateY(-1px)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]:hover .continue-arrow[_ngcontent-%COMP%]{opacity:1;transform:translate(2px)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]:active{box-shadow:0 1px 4px #2563eb33;transform:translateY(0) scale(.98)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]:focus-visible{outline:3px solid rgba(59,130,246,.25);outline-offset:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .llm-resume-hint[_ngcontent-%COMP%]{color:#94a3b8;font-size:11px;line-height:1.35;white-space:nowrap}@media(max-width:560px){.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .llm-resume-row[_ngcontent-%COMP%]{width:100%}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .btn-continue-task[_ngcontent-%COMP%]{flex:1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .llm-failure-stack[_ngcontent-%COMP%] .llm-resume-hint[_ngcontent-%COMP%]{display:none}}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:10px;padding:10px 12px;margin-top:0;margin-bottom:0;margin-left:-19px;box-shadow:0 4px 12px #00000005;width:380px;max-width:100%;display:flex;flex-direction:column;gap:8px;transition:all .25s cubic-bezier(.16,1,.3,1);position:relative;cursor:pointer;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-running[_ngcontent-%COMP%]{border-color:#3b82f659;box-shadow:0 2px 10px #3b82f614;animation:_ngcontent-%COMP%_subtle-card-glow 2.5s ease-in-out infinite alternate}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-running[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{color:#3b82f6}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-running[_ngcontent-%COMP%] .action-title[_ngcontent-%COMP%]{background:linear-gradient(90deg,#0f172a,#2563eb 30%,#7c3aed,#0284c7 70%,#0f172a);background-size:200% 100%;-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;animation:_ngcontent-%COMP%_subtle-color-flow 2.8s linear infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%]:hover{border-color:#cbd5e1;box-shadow:0 4px 16px #0000000a;transform:translateY(-1px)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%]:hover .action-expand-icon[_ngcontent-%COMP%]{color:#1e293b;background:#e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-expanded[_ngcontent-%COMP%]{border-color:#94a3b8;background:#fff;box-shadow:0 8px 24px #0000000f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-expanded[_ngcontent-%COMP%] .action-expand-icon[_ngcontent-%COMP%]{color:#0f172a;background:#e2e8f0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-failed[_ngcontent-%COMP%]{background:#f8fafc;border-color:#fca5a5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-failed.has-details[_ngcontent-%COMP%]{cursor:pointer}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-failed[_ngcontent-%COMP%]:hover{border-color:#f87171}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.is-failed.is-expanded[_ngcontent-%COMP%]{background:#fff;border-color:#f87171}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%]{background:#f0fdf4;border:1px solid #bbf7d0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-completed[_ngcontent-%COMP%]{background:#f0fdf4;border-color:#86efac}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-completed[_ngcontent-%COMP%] .report-icon[_ngcontent-%COMP%]{color:#16a34a;font-variation-settings:"FILL" 1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-completed[_ngcontent-%COMP%] .action-title[_ngcontent-%COMP%]{color:#14532d;font-weight:700}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-completed[_ngcontent-%COMP%] .action-status-badge.is-completed[_ngcontent-%COMP%]{background:#dcfce7;color:#15803d;border:1px solid #bbf7d0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-failed[_ngcontent-%COMP%]{background:#fef2f2;border-color:#fecaca}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-failed[_ngcontent-%COMP%] .report-icon[_ngcontent-%COMP%]{color:#dc2626;font-variation-settings:"FILL" 1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-failed[_ngcontent-%COMP%] .action-title[_ngcontent-%COMP%]{color:#7f1d1d;font-weight:700}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card.is-failed[_ngcontent-%COMP%] .action-status-badge.is-failed[_ngcontent-%COMP%]{background:#fee2e2;color:#b91c1c;border:1px solid #fca5a5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%]{font-size:12px;line-height:1.5;color:#1f2937;font-weight:400;-webkit-user-select:text;user-select:text;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:3px 0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#0f172a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:3px 0;padding-left:18px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.task-report-card[_ngcontent-%COMP%] .report-markdown-body[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:Fira Code,monospace;background:#e2e8f0;color:#1f2937;padding:1px 4px;border-radius:4px;font-size:11px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.has-no-details[_ngcontent-%COMP%]{padding:8px 12px;gap:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card.has-no-details[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%]{border-bottom:none;padding-bottom:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;border-bottom:1px solid #f1f5f9;padding-bottom:6px;width:100%}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-icon[_ngcontent-%COMP%]{font-size:18px;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-icon.is-failed[_ngcontent-%COMP%]{color:#ef4444}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-title[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,Liberation Sans,sans-serif;font-weight:700;font-size:12px;color:#0f172a;flex-grow:1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-agent-badge[_ngcontent-%COMP%]{display:inline-flex;align-items:center;font-size:10px;font-weight:600;padding:1px 6px;border-radius:4px;background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;letter-spacing:.2px;margin-right:6px;white-space:nowrap}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-agent-badge.is-outputter[_ngcontent-%COMP%]{background:linear-gradient(135deg,#f5f3fff2,#eef2fff2);color:#6d28d9;border-color:#c4b5fd}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:3px;font-size:11px;font-weight:600;padding:1px 6px;border-radius:4px;margin-left:auto;white-space:nowrap}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{font-size:13px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge.is-failed[_ngcontent-%COMP%]{background:#fef2f2;color:#dc2626;border:1px solid #fee2e2}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge.is-failed[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{color:#ef4444}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge.is-paused[_ngcontent-%COMP%]{background:#fff7ed;color:#c2410c;border:1px solid #fed7aa}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-status-badge.is-paused[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{color:#f97316}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-header[_ngcontent-%COMP%] .action-expand-icon[_ngcontent-%COMP%]{font-size:18px;color:#94a3b8;margin-left:4px;padding:2px;border-radius:50%;transition:all .2s ease;display:flex;align-items:center;justify-content:center}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%]{display:flex;align-items:baseline;font-size:12px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%] .detail-label[_ngcontent-%COMP%]{color:#64748b;font-weight:500;width:85px;flex-shrink:0}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%] .detail-value[_ngcontent-%COMP%]{color:#334155;font-weight:600;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%] .detail-value.coordinate-badge[_ngcontent-%COMP%]{background:#e2e8f0;color:#475569;padding:2px 6px;border-radius:4px;font-family:Fira Code,monospace;font-size:11px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%] .detail-value.command-value[_ngcontent-%COMP%]{font-family:Fira Code,Consolas,Monaco,monospace;background:#f1f5f9;border:1px solid #e2e8f0;padding:2px 6px;border-radius:4px;font-size:11px;font-weight:500;color:#0f172a;white-space:pre-wrap;word-break:break-all}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .action-detail-row[_ngcontent-%COMP%] .detail-value.command-value[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:inherit;font-size:inherit;color:inherit;background:none;padding:0;border:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .failure-retry-details[_ngcontent-%COMP%]{margin-top:4px;padding-top:9px;border-top:1px solid #fecaca;display:flex;flex-direction:column;gap:7px;animation:_ngcontent-%COMP%_fadeInDetails .2s ease-out}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .failure-retry-details[_ngcontent-%COMP%] .failure-retry-title[_ngcontent-%COMP%]{color:#991b1b;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .failure-retry-details[_ngcontent-%COMP%] .failure-retry-row[_ngcontent-%COMP%]{display:flex;justify-content:space-between;gap:12px;color:#7f1d1d;font-size:10.5px;line-height:1.4}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-body[_ngcontent-%COMP%] .failure-retry-details[_ngcontent-%COMP%] .failure-retry-row[_ngcontent-%COMP%] span[_ngcontent-%COMP%]:last-child{color:#64748b;text-align:right}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px;padding-top:4px;animation:_ngcontent-%COMP%_fadeInDetails .2s ease-out}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%]{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;background:#f8fafc;padding:10px;border-radius:8px;border:1px solid #f1f5f9}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item.full-width[_ngcontent-%COMP%]{grid-column:1/-1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item[_ngcontent-%COMP%] .grid-label[_ngcontent-%COMP%]{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:#64748b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item[_ngcontent-%COMP%] .grid-value[_ngcontent-%COMP%]{font-size:12px;font-weight:600;color:#1e293b;word-break:break-all}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item[_ngcontent-%COMP%] .grid-value.code-pill[_ngcontent-%COMP%]{font-family:Roboto Mono,Fira Code,Consolas,monospace;font-size:11px;background:#e2e8f0;color:#334155;padding:2px 6px;border-radius:4px;display:inline-block;width:fit-content}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-grid[_ngcontent-%COMP%] .grid-item[_ngcontent-%COMP%] .grid-value.command-value[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:Roboto Mono,Fira Code,Consolas,monospace;font-size:11px;background:#0f172a;color:#38bdf8;padding:4px 8px;border-radius:4px;display:block;word-break:break-all;white-space:pre-wrap}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%]{display:flex;flex-direction:row;flex-wrap:nowrap;gap:12px;margin-top:4px;justify-content:flex-start;width:100%}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%]{flex:1 1 0;min-width:0;max-width:320px;display:flex;flex-direction:column;gap:6px;align-items:center}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .screenshot-label[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,Arial,Liberation Sans,sans-serif;font-size:11.5px;font-weight:500;color:#64748b;letter-spacing:normal;text-transform:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .image-wrapper[_ngcontent-%COMP%]{display:inline-flex;justify-content:center;align-items:center;max-width:100%;border:1px solid #e2e8f0;border-radius:8px;background-color:#f8fafc;cursor:zoom-in;overflow:hidden;box-shadow:0 1px 3px #0000000d;transition:transform .15s ease,box-shadow .15s ease,border-color .15s ease}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .image-wrapper[_ngcontent-%COMP%]:hover{transform:translateY(-1px);box-shadow:0 4px 12px #00000014;border-color:#cbd5e1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .image-wrapper[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%]{position:relative;display:inline-block;max-width:100%}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .image-wrapper[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{display:block;max-width:100%;max-height:280px;width:auto;height:auto;margin:0 auto;border-radius:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-screenshots-container[_ngcontent-%COMP%] .screenshot-column[_ngcontent-%COMP%] .image-wrapper[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%] .overlay-layer[_ngcontent-%COMP%]{position:absolute;inset:0;pointer-events:none;overflow:visible}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-error-banner[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;background:#fff1f2;border:1px solid #fecdd3;color:#be123c;padding:8px 12px;border-radius:6px;font-size:12px;font-weight:500}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .step-android-action-card[_ngcontent-%COMP%] .action-card-expanded-details[_ngcontent-%COMP%] .expanded-error-banner[_ngcontent-%COMP%] .icon[_ngcontent-%COMP%]{font-size:16px;color:#e11d48}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%]{padding:20px 24px;display:flex;flex-direction:column;gap:12px;background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:12px;box-shadow:0 4px 6px -1px #00000003,0 2px 4px -1px #00000002}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-header[_ngcontent-%COMP%]{font-weight:500;font-size:12px;color:#71717a;display:flex;align-items:center;gap:6px;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%]{font-size:12px;line-height:1.6;color:#27272a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{font-weight:600;color:#09090b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] ul[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] ol[_ngcontent-%COMP%]{margin:6px 0;padding-left:20px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] ul[_ngcontent-%COMP%] li[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] ol[_ngcontent-%COMP%] li[_ngcontent-%COMP%]{margin-bottom:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] div[_ngcontent-%COMP%]{margin-bottom:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item[_ngcontent-%COMP%] .step-body[_ngcontent-%COMP%] .step-actions-container[_ngcontent-%COMP%] .final-summary-card[_ngcontent-%COMP%] .final-summary-body[_ngcontent-%COMP%] .md-para-gap[_ngcontent-%COMP%]{height:8px}@keyframes _ngcontent-%COMP%_spin-loader{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes _ngcontent-%COMP%_pulse-green{0%{box-shadow:0 0 #10b98166}70%{box-shadow:0 0 0 6px #10b98100}to{box-shadow:0 0 #10b98100}}@keyframes _ngcontent-%COMP%_pulse-stream-blue{0%{box-shadow:0 0 #0ea5e966}70%{box-shadow:0 0 0 6px #0ea5e900}to{box-shadow:0 0 #0ea5e900}}@keyframes _ngcontent-%COMP%_pulse-stream-green{0%{box-shadow:0 0 #22c55e66}70%{box-shadow:0 0 0 6px #22c55e00}to{box-shadow:0 0 #22c55e00}}@keyframes _ngcontent-%COMP%_pulse-stream-purple{0%{box-shadow:0 0 #a855f766}70%{box-shadow:0 0 0 6px #a855f700}to{box-shadow:0 0 #a855f700}}@keyframes _ngcontent-%COMP%_pulse-stream-grey{0%{box-shadow:0 0 #71717a66}70%{box-shadow:0 0 0 6px #71717a00}to{box-shadow:0 0 #71717a00}}@keyframes _ngcontent-%COMP%_pulse-black-dot{0%{box-shadow:0 0 #0006}70%{box-shadow:0 0 0 5px #0000}to{box-shadow:0 0 #0000}}@keyframes _ngcontent-%COMP%_pulse-black-dot-small{0%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes _ngcontent-%COMP%_fadeInDetails{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_dropdownFadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.image-modal-backdrop[_ngcontent-%COMP%]{position:absolute;inset:0;width:100%;height:100%;background-color:#0f172abf;-webkit-backdrop-filter:blur(4px);backdrop-filter:blur(4px);z-index:1000;display:flex;align-items:center;justify-content:center;padding:24px;box-sizing:border-box;animation:_ngcontent-%COMP%_fadeInModal .2s ease-out}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%]{position:relative;max-width:calc(100% - 48px);max-height:calc(100% - 48px);background:#0f172a;border-radius:12px;padding:12px;box-shadow:0 20px 25px -5px #00000080,0 8px 10px -6px #00000080;display:flex;align-items:center;justify-content:center;box-sizing:border-box}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .image-modal-close[_ngcontent-%COMP%]{position:absolute;top:-12px;right:-12px;width:32px;height:32px;border-radius:50%;background:#ef4444;color:#fff;border:2px solid white;display:flex;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 4px 6px -1px #0000004d;transition:transform .15s ease,background-color .15s ease;z-index:20}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .image-modal-close[_ngcontent-%COMP%]:hover{transform:scale(1.1);background:#dc2626}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .image-modal-close[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%]{position:relative;display:inline-flex;align-items:center;justify-content:center;max-width:100%;max-height:100%}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%] img[_ngcontent-%COMP%]{display:block;max-width:100%;max-height:calc(100vh - 140px);width:auto;height:auto;object-fit:contain;border-radius:6px}.image-modal-backdrop[_ngcontent-%COMP%] .image-modal-content[_ngcontent-%COMP%] .img-relative-box[_ngcontent-%COMP%] .overlay-layer[_ngcontent-%COMP%]{position:absolute;inset:0;pointer-events:none;overflow:visible;border-radius:6px}@keyframes _ngcontent-%COMP%_pulse-animation{0%{transform:scale(.5);opacity:1}to{transform:scale(1.8);opacity:0}}@keyframes _ngcontent-%COMP%_fadeInModal{0%{opacity:0}to{opacity:1}}@keyframes _ngcontent-%COMP%_subtle-color-flow{0%{background-position:0% 50%}to{background-position:200% 50%}}@keyframes _ngcontent-%COMP%_pulse-stream-dot{0%,to{transform:scale(1);box-shadow:0 0 #3b82f673}50%{transform:scale(1.15);box-shadow:0 0 0 4px #3b82f600}}@keyframes _ngcontent-%COMP%_subtle-card-glow{0%{border-color:#3b82f640;box-shadow:0 2px 8px #3b82f60d}to{border-color:#6366f173;box-shadow:0 4px 14px #6366f11f}}.outputter-synthesis-stream-card[_ngcontent-%COMP%]{margin:16px 0 12px;border-radius:12px;border:1px solid rgba(196,181,253,.85);background:linear-gradient(135deg,#f5f3fff2,#faf5fffa);box-shadow:0 4px 16px #7c3aed14,0 1px 3px #00000008;overflow:hidden;animation:_ngcontent-%COMP%_fadeInModal .25s ease-out}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:#ede9fe99;border-bottom:1px solid rgba(196,181,253,.7);gap:12px;flex-wrap:wrap}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .icon-sparkle[_ngcontent-%COMP%]{font-size:22px;color:#7c3aed}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .main-title[_ngcontent-%COMP%]{font-size:14px;font-weight:700;color:#4338ca}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-open-note[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;background:linear-gradient(135deg,#6366f1,#7c3aed);color:#fff;border:none;border-radius:8px;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s ease;box-shadow:0 2px 6px #6366f140}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-open-note[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-open-note[_ngcontent-%COMP%]:hover{background:linear-gradient(135deg,#4f46e5,#6d28d9);box-shadow:0 4px 10px #6366f159;transform:translateY(-1px)}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%]{padding:14px 16px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:10px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-note-title[_ngcontent-%COMP%]{font-size:14px;font-weight:700;color:#1e1b4b;margin:0}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-milestones[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-milestones[_ngcontent-%COMP%] .milestone-item[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.45;color:#374151}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-milestones[_ngcontent-%COMP%] .milestone-item[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{font-size:16px;color:#7c3aed;margin-top:1px;flex-shrink:0}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-milestones[_ngcontent-%COMP%] .milestone-item.status-checked[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{color:#10b981}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-milestones[_ngcontent-%COMP%] .milestone-item.status-progress[_ngcontent-%COMP%] .status-icon[_ngcontent-%COMP%]{color:#f59e0b}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-other-lines[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-other-lines[_ngcontent-%COMP%] .other-line[_ngcontent-%COMP%]{margin:0;font-size:12.5px;line-height:1.5;color:#4b5563}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-other-lines[_ngcontent-%COMP%] .other-line[_ngcontent-%COMP%] strong[_ngcontent-%COMP%]{color:#1e1b4b}.outputter-synthesis-stream-card[_ngcontent-%COMP%] .synthesis-card-preview[_ngcontent-%COMP%] .parsed-note-container[_ngcontent-%COMP%] .parsed-other-lines[_ngcontent-%COMP%] .other-line[_ngcontent-%COMP%] code[_ngcontent-%COMP%]{font-family:JetBrains Mono,monospace;background:#ffffffe6;border:1px solid #e5e7eb;padding:1px 4px;border-radius:4px;color:#6d28d9;font-size:11.5px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%]{border-left:2px solid #ddd6fe;padding-left:12px;margin-left:-14px;font-family:Google Sans,Google Sans Text,Inter,Arial,sans-serif}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-index[_ngcontent-%COMP%]{flex:0 0 14px;text-align:right;color:#a1a1aa;font-variant-numeric:tabular-nums;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px;margin-bottom:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;min-height:20px;font-size:12px;line-height:1.5;letter-spacing:.2px;color:#71717a;-webkit-user-select:none;user-select:none}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%] .stream-status-dot[_ngcontent-%COMP%]{flex:0 0 auto;width:8px;height:8px;border-radius:50%;display:inline-block;background:#d4d4d8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%] .stream-status-dot.streaming[_ngcontent-%COMP%]{background:#7c3aed;box-shadow:0 0 #7c3aed73;animation:_ngcontent-%COMP%_pulse-stream-dot 1.8s cubic-bezier(.4,0,.6,1) infinite}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%] .check-title[_ngcontent-%COMP%]{flex:0 0 auto;font-weight:500;color:#52525b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%] .check-subject[_ngcontent-%COMP%]{flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-header[_ngcontent-%COMP%] .check-status[_ngcontent-%COMP%]{flex:0 0 auto;margin-left:auto;font-weight:500;color:#52525b;white-space:nowrap}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane.tone-passed[_ngcontent-%COMP%] .check-status[_ngcontent-%COMP%]{color:#15803d}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane.tone-failed[_ngcontent-%COMP%] .check-status[_ngcontent-%COMP%]{color:#b91c1c}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane.tone-inconclusive[_ngcontent-%COMP%] .check-status[_ngcontent-%COMP%]{color:#b45309}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane.tone-running[_ngcontent-%COMP%] .check-status[_ngcontent-%COMP%]{color:#6d28d9}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane.tone-muted[_ngcontent-%COMP%]{opacity:.6}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-items[_ngcontent-%COMP%]{list-style:none;margin:0;padding:0 0 0 16px;display:flex;flex-direction:column;gap:2px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-items[_ngcontent-%COMP%] .check-item[_ngcontent-%COMP%]{display:flex;align-items:baseline;gap:8px;font-size:12px;line-height:1.5;color:#3f3f46}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-items[_ngcontent-%COMP%] .check-item[_ngcontent-%COMP%] .check-text[_ngcontent-%COMP%]{flex:1 1 auto;min-width:0;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-lane[_ngcontent-%COMP%] .check-items[_ngcontent-%COMP%] .check-item[_ngcontent-%COMP%] .check-method[_ngcontent-%COMP%]{flex:0 0 auto;color:#a1a1aa;white-space:nowrap}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:4px;margin-top:8px;padding-left:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:1.5;color:#52525b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict[_ngcontent-%COMP%] .check-index[_ngcontent-%COMP%]{line-height:18px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict[_ngcontent-%COMP%] .verdict-icon[_ngcontent-%COMP%]{flex:0 0 auto;font-size:15px;line-height:18px;color:#a1a1aa}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict.passed[_ngcontent-%COMP%] .verdict-icon[_ngcontent-%COMP%]{color:#16a34a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict.failed[_ngcontent-%COMP%] .verdict-icon[_ngcontent-%COMP%]{color:#dc2626}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict.inconclusive[_ngcontent-%COMP%] .verdict-icon[_ngcontent-%COMP%]{color:#d97706}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict.superseded[_ngcontent-%COMP%], .agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict.unchecked[_ngcontent-%COMP%]{color:#a1a1aa}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict[_ngcontent-%COMP%] .verdict-body[_ngcontent-%COMP%]{flex:1 1 auto;min-width:0;word-break:break-word}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-verdict[_ngcontent-%COMP%] .verdict-suggestion[_ngcontent-%COMP%]{display:block;color:#1d4ed8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .phase-body[_ngcontent-%COMP%] .block-item.type-checker[_ngcontent-%COMP%] .check-result[_ngcontent-%COMP%] .check-note[_ngcontent-%COMP%]{font-size:12px;line-height:1.5;color:#71717a;padding-left:22px;word-break:break-word}']})};function f3(n,t){if(n&1){let e=L();l(0,"button",10),k("click",function(){v(e);let i=m();return y(i.clearHistory())}),l(1,"span",11),u(2,"delete"),d()()}if(n&2){let e=m();R("disabled",e.isSubmitting())}}function m3(n,t){n&1&&(l(0,"div",14),u(1," No active or queued tasks. "),d())}function _3(n,t){n&1&&$(0,"span",22)}function h3(n,t){if(n&1&&(l(0,"span",24)(1,"span",33),u(2,"phone_android"),d(),l(3,"span",34),u(4),d()()),n&2){let e=m().$implicit,o=m(2);R("title","Device: "+o.getDeviceSerial(e)),g(4),O(o.getDeviceSerial(e))}}function C3(n,t){n&1&&(l(0,"span",25),u(1,"\xB7"),d())}function M3(n,t){if(n&1){let e=L();l(0,"button",35),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.stopTask(r.session_id,i))}),l(1,"span",11),u(2,"stop_circle"),d()()}}function b3(n,t){if(n&1){let e=L();l(0,"button",36),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.deleteTask(r.session_id,i))}),l(1,"span",11),u(2,"delete"),d()()}if(n&2){let e=m(3);R("disabled",e.isSubmitting())}}function P3(n,t){if(n&1){let e=L();l(0,"div",19),k("click",function(){let i=v(e).$implicit,r=m(2);return y(r.selectTask(i.session_id))}),l(1,"div",20)(2,"span",21),M(3,_3,1,0,"span",22),u(4),it(5,"uppercase"),d(),l(6,"div",23),M(7,h3,5,2,"span",24),M(8,C3,2,0,"span",25),l(9,"span",26),u(10),it(11,"date"),d(),M(12,M3,3,0,"button",27),M(13,b3,3,1,"button",28),d()(),l(14,"div",29),u(15),d(),l(16,"div",30)(17,"span",31),u(18),d(),l(19,"span",32),u(20),it(21,"date"),d()()()}if(n&2){let e=t.$implicit,o=m(2);w("selected",o.agentService.currentSessionId()===e.session_id)("running",o.getTaskStatus(e)==="running"),g(2),R("ngClass",o.getTaskStatus(e)),g(),b(o.getTaskStatus(e)==="running"?3:-1),g(),V(" ",wi(5,18,o.getTaskStatus(e))," "),g(3),b(o.getDeviceSerial(e)?7:-1),g(),b(o.getDeviceSerial(e)?8:-1),g(2),O(Et(11,20,e.start_time*1e3,"shortTime")),g(2),b(o.getTaskStatus(e)==="running"||o.getTaskStatus(e)==="paused"?12:-1),g(),b(o.getTaskStatus(e)!=="running"&&o.getTaskStatus(e)!=="paused"?13:-1),g(),R("title",xi(e.initial_goal)),g(),V(" ",e.initial_goal||"Unnamed Task"," "),g(2),R("title",e.session_id),g(),V("ID: ",e.session_id),g(2),O(Et(21,23,e.start_time*1e3,"MM/dd/yy"))}}function O3(n,t){n&1&&(l(0,"div",14),u(1," No past tasks. "),d())}function v3(n,t){n&1&&$(0,"span",22)}function y3(n,t){if(n&1&&(l(0,"span",24)(1,"span",33),u(2,"phone_android"),d(),l(3,"span",34),u(4),d()()),n&2){let e=m().$implicit,o=m(2);R("title","Device: "+o.getDeviceSerial(e)),g(4),O(o.getDeviceSerial(e))}}function x3(n,t){n&1&&(l(0,"span",25),u(1,"\xB7"),d())}function w3(n,t){if(n&1){let e=L();l(0,"button",36),k("click",function(i){v(e);let r=m().$implicit,a=m(2);return y(a.deleteTask(r.session_id,i))}),l(1,"span",11),u(2,"delete"),d()()}if(n&2){let e=m(3);R("disabled",e.isSubmitting())}}function S3(n,t){if(n&1){let e=L();l(0,"div",19),k("click",function(){let i=v(e).$implicit,r=m(2);return y(r.selectTask(i.session_id))}),l(1,"div",20)(2,"span",21),M(3,v3,1,0,"span",22),u(4),it(5,"uppercase"),d(),l(6,"div",23),M(7,y3,5,2,"span",24),M(8,x3,2,0,"span",25),l(9,"span",26),u(10),it(11,"date"),d(),M(12,w3,3,1,"button",28),d()(),l(13,"div",29),u(14),d(),l(15,"div",30)(16,"span",31),u(17),d(),l(18,"span",32),u(19),it(20,"date"),d()()()}if(n&2){let e=t.$implicit,o=m(2);w("selected",o.agentService.currentSessionId()===e.session_id)("running",o.getTaskStatus(e)==="running"),g(2),R("ngClass",o.getTaskStatus(e)),g(),b(o.getTaskStatus(e)==="running"?3:-1),g(),V(" ",wi(5,17,o.getTaskStatus(e))," "),g(3),b(o.getDeviceSerial(e)?7:-1),g(),b(o.getDeviceSerial(e)?8:-1),g(2),O(Et(11,19,e.start_time*1e3,"shortTime")),g(2),b(o.getTaskStatus(e)!=="running"&&o.getTaskStatus(e)!=="paused"?12:-1),g(),R("title",xi(e.initial_goal)),g(),V(" ",e.initial_goal||"Unnamed Task"," "),g(2),R("title",e.session_id),g(),V("ID: ",e.session_id),g(2),O(Et(20,22,e.start_time*1e3,"MM/dd/yy"))}}function k3(n,t){if(n&1&&(l(0,"div",12)(1,"span",13),u(2,"bolt"),d(),u(3),d(),M(4,m3,2,0,"div",14),G(5,P3,22,26,"div",15,De().trackSession,!0),l(7,"details",16)(8,"summary",17)(9,"span",13),u(10,"history"),d(),u(11),d(),l(12,"div",18),M(13,O3,2,0,"div",14),G(14,S3,21,25,"div",15,De().trackSession,!0),d()()),n&2){let e=m();g(3),V(" Active Queue (",e.activeQueue().length,") "),g(),b(e.activeQueue().length===0?4:-1),g(),q(e.activeQueue()),g(6),V(" Past Runs & History (",e.historyTasks().length,") "),g(2),b(e.historyTasks().length===0?13:-1),g(),q(e.historyTasks())}}function E3(n,t){if(n&1){let e=L();l(0,"button",42),k("click",function(){let i=v(e).$implicit,r=m(3);return y(r.selectNote(i))}),l(1,"span",43),u(2,"draft"),d(),u(3),d()}if(n&2){let e=t.$implicit,o=m(3);w("active",o.agentService.selectedNoteKey()===e),g(3),V(" ",e," ")}}function D3(n,t){if(n&1&&(l(0,"div",37)(1,"div",40),G(2,E3,4,3,"button",41,De().trackNoteKey,!0),d()()),n&2){let e=m(2);g(2),q(e.noteKeys())}}function I3(n,t){n&1&&(l(0,"div",38)(1,"div",44)(2,"span",11),u(3,"description"),d()(),l(4,"h4",45),u(5,"No Notes & Plans"),d(),l(6,"p",46),u(7,"No notes or structured plans were recorded for this session."),d()())}function T3(n,t){if(n&1&&(l(0,"h2",47),u(1),d()),n&2){let e=m(3);g(),O(e.parsedNote().title)}}function A3(n,t){n&1&&(l(0,"span",52),u(1,"check_box"),d())}function N3(n,t){n&1&&(l(0,"span",53),u(1,"indeterminate_check_box"),d())}function R3(n,t){n&1&&(l(0,"span",54),u(1,"check_box_outline_blank"),d())}function F3(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function L3(n,t){n&1&&(l(0,"span",61),u(1,"check_box"),d())}function V3(n,t){n&1&&(l(0,"span",62),u(1,"indeterminate_check_box"),d())}function j3(n,t){n&1&&(l(0,"span",63),u(1,"check_box_outline_blank"),d())}function $3(n,t){n&1&&(l(0,"span",64),u(1,"\u2022"),d())}function z3(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function B3(n,t){if(n&1&&(l(0,"span",65),G(1,z3,2,5,"span",56,Ie),d()),n&2){let e=m().$implicit;g(),q(e.segments)}}function H3(n,t){if(n&1&&(l(0,"div",60),M(1,L3,2,0,"span",61)(2,V3,2,0,"span",62)(3,j3,2,0,"span",63)(4,$3,2,0,"span",64),M(5,B3,3,0,"span",65),d()),n&2){let e,o=t.$implicit;Se("padding-left",o.indent>2?(o.indent-2)*12:0,"px"),R("ngClass",o.type),g(),b((e=o.type)==="checked"?1:e==="progress"?2:e==="unchecked"?3:e==="list-item"?4:-1),g(4),b(o.type!=="empty"?5:-1)}}function U3(n,t){if(n&1&&(l(0,"div",57),G(1,H3,6,5,"div",59,De().trackMarkdownLine,!0),d()),n&2){let e=m().$implicit;g(),q(e.subSteps)}}function G3(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function q3(n,t){if(n&1&&(l(0,"div",67)(1,"div",68)(2,"span",69),u(3),d()(),l(4,"div",70),G(5,G3,2,5,"span",56,Ie),d()()),n&2){let e=t.$implicit;Se("margin-left",e.indent>2?(e.indent-2)*12:0,"px"),R("ngClass",e.type),g(2),w("assert",e.checkKind==="assert"),g(),We("",e.checkKind||"verify","",e.atEnd?" @end":""),g(2),q(e.segments)}}function W3(n,t){if(n&1&&(l(0,"div",58),G(1,q3,7,7,"div",66,De().trackMarkdownLine,!0),d()),n&2){let e=m().$implicit;g(),q(e.checks)}}function K3(n,t){if(n&1&&(l(0,"div",50)(1,"div",51),M(2,A3,2,0,"span",52)(3,N3,2,0,"span",53)(4,R3,2,0,"span",54),l(5,"div",55),G(6,F3,2,5,"span",56,Ie),d()(),M(8,U3,3,0,"div",57),M(9,W3,3,0,"div",58),d()),n&2){let e,o=t.$implicit;R("ngClass",o.type),g(2),b((e=o.type)==="checked"?2:e==="progress"?3:e==="unchecked"?4:-1),g(4),q(o.segments),g(2),b(o.subSteps.length>0?8:-1),g(),b(o.checks.length>0?9:-1)}}function Y3(n,t){if(n&1&&(l(0,"div",48),G(1,K3,10,4,"div",50,De().trackMilestone,!0),d()),n&2){let e=m(3);g(),q(e.parsedNote().milestones)}}function Z3(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function Q3(n,t){if(n&1&&(l(0,"div",73)(1,"div",68)(2,"span",69),u(3),d()(),l(4,"div",70),G(5,Z3,2,5,"span",56,Ie),d()()),n&2){let e=m().$implicit;Se("margin-left",e.indent*12,"px"),R("ngClass",e.type),g(2),w("assert",e.checkKind==="assert"),g(),We("",e.checkKind||"verify","",e.atEnd?" @end":""),g(2),q(e.segments)}}function J3(n,t){n&1&&(l(0,"span",61),u(1,"check_box"),d())}function X3(n,t){n&1&&(l(0,"span",62),u(1,"indeterminate_check_box"),d())}function e4(n,t){n&1&&(l(0,"span",63),u(1,"check_box_outline_blank"),d())}function t4(n,t){n&1&&(l(0,"span",64),u(1,"\u2022"),d())}function n4(n,t){if(n&1&&(l(0,"span"),u(1),d()),n&2){let e=t.$implicit;w("bold-segment",e.bold)("code-segment",e.code),g(),O(e.text)}}function o4(n,t){if(n&1&&(l(0,"span",65),G(1,n4,2,5,"span",56,Ie),d()),n&2){let e=m(2).$implicit;g(),q(e.segments)}}function i4(n,t){if(n&1&&(l(0,"div",74),M(1,J3,2,0,"span",61)(2,X3,2,0,"span",62)(3,e4,2,0,"span",63)(4,t4,2,0,"span",64),M(5,o4,3,0,"span",65),d()),n&2){let e,o=m().$implicit;Se("margin-left",o.indent*12,"px"),R("ngClass",o.type),g(),b((e=o.type)==="checked"?1:e==="progress"?2:e==="unchecked"?3:e==="list-item"?4:-1),g(4),b(o.type!=="empty"?5:-1)}}function r4(n,t){if(n&1&&M(0,Q3,7,7,"div",71)(1,i4,6,5,"div",72),n&2){let e=t.$implicit;b(e.type==="verify"||e.type==="finding"?0:1)}}function a4(n,t){if(n&1&&(l(0,"div",49),G(1,r4,2,1,null,null,De().trackMarkdownLine,!0),d()),n&2){let e=m(3);g(),q(e.parsedNote().otherLines)}}function s4(n,t){if(n&1&&(l(0,"div",39),M(1,T3,2,1,"h2",47),M(2,Y3,3,0,"div",48),M(3,a4,3,0,"div",49),d()),n&2){let e=m(2);g(),b(e.parsedNote().title?1:-1),g(),b(e.parsedNote().milestones.length>0?2:-1),g(),b(e.parsedNote().otherLines.length>0?3:-1)}}function c4(n,t){if(n&1&&(l(0,"div",9),M(1,D3,4,0,"div",37),M(2,I3,8,0,"div",38),M(3,s4,4,3,"div",39),d()),n&2){let e=m();g(),b(e.noteKeys().length>1?1:-1),g(),b(e.noteKeys().length===0?2:-1),g(),b(e.noteKeys().length>0?3:-1)}}var qr=class n{agentService=D(It);taskInput="";isSubmitting=x(!1);errorMessage=x(null);deviceSerialCache=new WeakMap;activeQueue=I(()=>this.agentService.sessions().filter(e=>{let o=this.getTaskStatus(e);return o==="running"||o==="paused"||o==="pending"}).sort((e,o)=>{let i=this.getTaskStatus(e),r=this.getTaskStatus(o),a=i==="running"||i==="paused",s=r==="running"||r==="paused";return a&&!s?-1:!a&&s?1:(e.start_time||0)-(o.start_time||0)}));historyTasks=I(()=>this.agentService.sessions().filter(t=>{let e=this.getTaskStatus(t);return e==="completed"||e==="failed"||e==="cancelled"}));submitTask(){let t=this.taskInput.trim();t&&(this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.runTask(t).subscribe({next:e=>{this.taskInput="",this.isSubmitting.set(!1),this.agentService.fetchStatus()},error:e=>{console.error("Failed to submit task:",e),this.isSubmitting.set(!1),this.errorMessage.set(e.error?.detail||"The runner is busy. Please wait for the current task to finish."),setTimeout(()=>{this.errorMessage.set(null)},5e3)}}))}stopTask(t,e){e.stopPropagation(),this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.stopTask(t,!1),setTimeout(()=>{this.isSubmitting.set(!1)},400)}stopTasks(t=!1){this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.stopTask(t),setTimeout(()=>{this.isSubmitting.set(!1)},400)}clearHistory(){confirm("Are you sure you want to clear all tasks and history? This cannot be undone.")&&(this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.clearAllHistory().subscribe({next:()=>{this.isSubmitting.set(!1)},error:t=>{console.error("Failed to clear history:",t),this.isSubmitting.set(!1),this.errorMessage.set(t.error?.detail||"Failed to clear history.")}}))}deleteTask(t,e){e.stopPropagation(),confirm("Are you sure you want to delete this task? This cannot be undone.")&&(this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.deleteSession(t).subscribe({next:()=>{this.isSubmitting.set(!1)},error:o=>{console.error(`Failed to delete task ${t}:`,o),this.isSubmitting.set(!1),this.errorMessage.set(o.error?.detail||"Failed to delete task.")}}))}getTaskStatus(t){if(t.status){let e=t.status.toLowerCase();if(e==="completed"||e==="success"||e==="failed"||e==="cancelled")return e==="success"?"completed":e;if(e==="running"||e==="paused"||e==="pending")return e}return t.session_id===this.agentService.runningSessionId()&&(this.agentService.agentStatus()==="running"||this.agentService.agentStatus()==="paused")?this.agentService.agentStatus():"completed"}getDeviceSerial(t){if(this.deviceSerialCache.has(t))return this.deviceSerialCache.get(t)??null;let e=null,o=t.device_serial||t.device_id;if(o&&o!=="pending"&&o!=="null"&&o!=="undefined")e=o;else if(t.device_info)try{let i=typeof t.device_info=="string"?JSON.parse(t.device_info):t.device_info,r=i?.device_id||i?.device_serial;r&&r!=="pending"&&r!=="null"&&r!=="undefined"&&(e=r)}catch(i){}return this.deviceSerialCache.set(t,e),e}selectTask(t){this.agentService.selectSession(t,!0)}currentNoteContent=I(()=>{let t=this.agentService.currentNotes(),e=this.agentService.selectedNoteKey();return t[e]||""});noteKeys=I(()=>Object.keys(this.agentService.currentNotes()).filter(t=>t.toLowerCase().endsWith(".md")));parsedNote=I(()=>this.getParsedNote(this.currentNoteContent()));getParsedNote(t){return ks(t)}getParsedNoteLines(t){return Ss(t)}selectNote(t){this.agentService.selectedNoteKey.set(t)}trackSession(t,e){return e.session_id}trackNoteKey(t,e){return e}trackMilestone(t,e){return e.index}trackMarkdownLine(t,e){return t}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-chat-interface"]],decls:18,vars:7,consts:[[1,"chat-interface-container"],[1,"chat-header"],[1,"tab-selector-bar"],[1,"tab-selector-btn",3,"click"],[1,"material-symbols-outlined","tab-icon"],[1,"tab-text"],[1,"header-right"],["title","Clear all tasks and history","aria-label","Clear all tasks and history",1,"btn-clear-history",3,"disabled"],[1,"chat-messages"],[1,"notes-tab-content"],["title","Clear all tasks and history","aria-label","Clear all tasks and history",1,"btn-clear-history",3,"click","disabled"],[1,"material-symbols-outlined"],[1,"queue-section-header"],[1,"material-symbols-outlined","section-icon"],[1,"empty-section-placeholder"],[1,"task-card",3,"selected","running"],["open","",1,"history-section"],[1,"history-summary"],[1,"history-list-wrapper"],[1,"task-card",3,"click"],[1,"task-card-header"],[1,"task-badge",3,"ngClass"],[1,"status-dot"],[1,"task-card-header-actions"],[1,"task-device",3,"title"],[1,"task-time-divider"],[1,"task-time"],["type","button","title","Stop this task","aria-label","Stop this task",1,"btn-stop-card"],["type","button","title","Delete this task","aria-label","Delete this task",1,"btn-delete-card",3,"disabled"],[1,"task-goal",3,"title"],[1,"task-card-footer"],[1,"task-id",3,"title"],[1,"task-date"],[1,"material-symbols-outlined","device-icon"],[1,"device-name"],["type","button","title","Stop this task","aria-label","Stop this task",1,"btn-stop-card",3,"click"],["type","button","title","Delete this task","aria-label","Delete this task",1,"btn-delete-card",3,"click","disabled"],[1,"notes-selector-container"],[1,"empty-notes-container"],[1,"note-viewer-cards"],[1,"notes-selector-list"],[1,"note-selector-pill",3,"active"],[1,"note-selector-pill",3,"click"],[1,"material-symbols-outlined","pill-icon"],[1,"empty-notes-icon"],[1,"empty-notes-title"],[1,"empty-notes-subtitle"],[1,"note-title"],[1,"milestones-container"],[1,"other-lines-container"],[1,"milestone-block",3,"ngClass"],[1,"milestone-title-row"],[1,"material-symbols-outlined","milestone-status-icon","checked"],[1,"material-symbols-outlined","milestone-status-icon","progress"],[1,"material-symbols-outlined","milestone-status-icon","unchecked"],[1,"milestone-text"],[3,"bold-segment","code-segment"],[1,"milestone-substeps"],[1,"milestone-checks-container"],[1,"substep-row",3,"ngClass","padding-left"],[1,"substep-row",3,"ngClass"],[1,"material-symbols-outlined","check-icon","checked"],[1,"material-symbols-outlined","check-icon","progress"],[1,"material-symbols-outlined","check-icon","unchecked"],[1,"bullet"],[1,"line-text"],[1,"verification-card",3,"ngClass","margin-left"],[1,"verification-card",3,"ngClass"],[1,"card-header"],[1,"card-tag"],[1,"card-body"],[1,"verification-card","standalone",3,"ngClass","margin-left"],[1,"md-line",3,"ngClass","margin-left"],[1,"verification-card","standalone",3,"ngClass"],[1,"md-line",3,"ngClass"]],template:function(e,o){e&1&&(l(0,"div",0)(1,"header",1)(2,"div",2)(3,"button",3),k("click",function(){return o.agentService.activeTab.set("tasks")}),l(4,"span",4),u(5,"list_alt"),d(),l(6,"span",5),u(7,"Task Queue"),d()(),l(8,"button",3),k("click",function(){return o.agentService.activeTab.set("notes")}),l(9,"span",4),u(10,"description"),d(),l(11,"span",5),u(12,"Notes & Plans"),d()()(),l(13,"div",6),M(14,f3,3,1,"button",7),d()(),l(15,"main",8),M(16,k3,16,4),M(17,c4,4,3,"div",9),d()()),e&2&&(g(3),w("active",o.agentService.activeTab()==="tasks"),g(5),w("active",o.agentService.activeTab()==="notes"),g(6),b(o.agentService.sessions().length>0&&o.agentService.activeTab()==="tasks"?14:-1),g(2),b(o.agentService.activeTab()==="tasks"?16:-1),g(),b(o.agentService.activeTab()==="notes"?17:-1))},dependencies:[No,ki,Qt,Ya,Ei],styles:['[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.chat-interface-container[_ngcontent-%COMP%]{container-type:inline-size;display:flex;flex-direction:column;height:100%;background:#fff;box-sizing:border-box}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;gap:16px;height:60px;padding:0 20px;border-bottom:1px solid rgba(0,0,0,.08);background:#fff;box-sizing:border-box}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%]{display:flex;height:36px;box-sizing:border-box;background:#f1f5f9b8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:18px;padding:3px;gap:3px;align-items:center;flex-shrink:0;box-shadow:0 2px 8px -1px #0f172a0a,inset 0 1px 1px #fffc}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:30px;padding:0 14px;box-sizing:border-box;border:none;background:transparent;border-radius:15px;font-size:13px;font-weight:600;color:#64748b;cursor:pointer;line-height:1;transition:all .2s cubic-bezier(.4,0,.2,1);white-space:nowrap}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{font-size:16px;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%]:hover:not(.active){color:#0f172a;background:#ffffffb3}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn.active[_ngcontent-%COMP%]{background:#fff;color:#2563eb;font-weight:700;box-shadow:0 2px 6px #0000000f,0 1px 2px #00000005}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:flex-end;flex:1 1 0;min-width:max-content;flex-shrink:0}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%]{padding:6px 14px;background:linear-gradient(135deg,#2563ebeb,#1a73e8d9);backdrop-filter:blur(14px) saturate(180%);-webkit-backdrop-filter:blur(14px) saturate(180%);border:1px solid rgba(255,255,255,.35);border-radius:16px;font-size:13.5px;font-weight:600;color:#fff;cursor:pointer;display:flex;align-items:center;gap:5px;transition:all .2s cubic-bezier(.4,0,.2,1);margin-right:8px;box-shadow:0 4px 14px #2563eb4d,inset 0 1px 1px #ffffff73}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%]:hover{background:linear-gradient(135deg,#2563eb,#1a73e8f2);border-color:#fff9;box-shadow:0 6px 18px #2563eb6b,inset 0 1px 1px #fff9;transform:translateY(-.5px)}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-resume-task[_ngcontent-%COMP%]:active{transform:scale(.97)}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%]{width:32px;height:32px;padding:0;box-sizing:border-box;background:#ffffffb8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(203,213,225,.85);border-radius:50%;color:#64748b;cursor:pointer;display:flex;align-items:center;justify-content:center;box-shadow:0 2px 6px -1px #0f172a0a,inset 0 1px 1px #ffffffe6;transition:all .2s cubic-bezier(.4,0,.2,1)}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%]:hover:not(:disabled){background:#fef2f2;color:#ef4444;border-color:#fca5a5;box-shadow:0 3px 10px #ef444426;transform:scale(1.05)}.chat-interface-container[_ngcontent-%COMP%] .chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%]:disabled{opacity:.5;cursor:not-allowed}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%]{flex:1;overflow-y:auto;padding:12px 12px 100px;display:flex;flex-direction:column;gap:8px;background:#fff}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%]::-webkit-scrollbar{width:5px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#94a3b84d;border-radius:4px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .queue-section-header[_ngcontent-%COMP%]{font-size:11.5px;font-weight:700;color:#475569;margin:6px 0 2px;display:flex;align-items:center;gap:6px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .queue-section-header[_ngcontent-%COMP%] .section-icon[_ngcontent-%COMP%]{font-size:14px;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .empty-section-placeholder[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;background:#f1f5f9;border:1px dashed #cbd5e1;border-radius:8px;padding:12px;color:#64748b;font-size:12px;text-align:center}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%]{margin-top:12px;border-top:1px solid rgba(226,232,240,.8);padding-top:12px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[open][_ngcontent-%COMP%] .history-summary[_ngcontent-%COMP%]{margin-bottom:8px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%] .history-summary[_ngcontent-%COMP%]{font-size:11.5px;font-weight:700;color:#475569;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;list-style:none;display:flex;align-items:center;gap:6px;padding:4px 6px;margin-left:-6px;border-radius:6px;transition:background .2s}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%] .history-summary[_ngcontent-%COMP%] .section-icon[_ngcontent-%COMP%]{font-size:14px;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%] .history-summary[_ngcontent-%COMP%]::-webkit-details-marker{display:none}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%] .history-summary[_ngcontent-%COMP%]:hover{background:#f1f5f9;color:#1e293b}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .history-section[_ngcontent-%COMP%] .history-list-wrapper[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]{background:#fff;border:1px solid rgba(226,232,240,.8);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:6px;cursor:pointer;position:relative;transition:all .2s cubic-bezier(.4,0,.2,1);box-shadow:0 1px 3px #00000003}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover{transform:translateY(-1px);border-color:#cbd5e1;box-shadow:0 3px 8px #94a3b814}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%]:has(.btn-delete-card) .task-time[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%]:has(.btn-delete-card) .task-time-divider[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%]:has(.btn-stop-card) .task-time[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%]:has(.btn-stop-card) .task-time-divider[_ngcontent-%COMP%]{opacity:0}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%]:hover .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]{opacity:1;pointer-events:auto}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card.selected[_ngcontent-%COMP%]{border-color:#3b82f6;box-shadow:0 0 0 1px #3b82f6,0 4px 12px #3b82f614;background:#fff}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card.running[_ngcontent-%COMP%]{border-color:#10b981;box-shadow:0 0 0 1px #10b981,0 4px 12px #10b98114}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card.running.selected[_ngcontent-%COMP%]{border-color:#3b82f6;box-shadow:0 0 0 1px #3b82f6,0 4px 12px #3b82f614}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center;gap:8px;min-width:0}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%]{flex-shrink:0;display:inline-flex;align-items:center;font-size:10.5px;font-weight:700;padding:2px 5px;border-radius:4px;letter-spacing:.05em;border:1px solid #e2e8f0;background:#fafafa;color:#64748b}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%]:before{content:"";width:4px;height:4px;border-radius:50%;display:inline-block;margin-right:4px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.running[_ngcontent-%COMP%]{background:#fafafa;color:#1f1f1f;border-color:#1f1f1f}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.running[_ngcontent-%COMP%]:before{background-color:#1f1f1f;animation:_ngcontent-%COMP%_pulse-black-dot-small 1.5s infinite ease-in-out}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.paused[_ngcontent-%COMP%]{background:#fff7ed;color:#c2410c;border-color:#fdba74}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.paused[_ngcontent-%COMP%]:before{background-color:#f97316}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.completed[_ngcontent-%COMP%]{background:#fafafa;color:#1f1f1f;border-color:#e2e8f0}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.completed[_ngcontent-%COMP%]:before{background-color:#1f1f1f}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.pending[_ngcontent-%COMP%]{background:#fafafa;color:#64748b;border-color:#e2e8f0}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.pending[_ngcontent-%COMP%]:before{background-color:#94a3b8}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.failed[_ngcontent-%COMP%]{background:#fafafa;color:#ef4444;border-color:#fee2e2}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.failed[_ngcontent-%COMP%]:before{background-color:#ef4444}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.cancelled[_ngcontent-%COMP%]{background:#fafafa;color:#d97706;border-color:#fef3c7}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge.cancelled[_ngcontent-%COMP%]:before{background-color:#f59e0b}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-badge[_ngcontent-%COMP%] .status-dot[_ngcontent-%COMP%]{display:none}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:4px;position:relative;min-width:0;flex-shrink:1;justify-content:flex-end}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-device[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:2.5px;min-width:0;flex-shrink:1;font-size:11px;color:#94a3b8;line-height:1;cursor:default;transition:color .15s ease,opacity .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-device[_ngcontent-%COMP%] .device-icon[_ngcontent-%COMP%]{font-size:12.5px;width:12.5px;height:12.5px;line-height:12.5px;color:#94a3b8;opacity:.75;flex-shrink:0}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-device[_ngcontent-%COMP%] .device-name[_ngcontent-%COMP%]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0;font-family:inherit;letter-spacing:-.15px}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-device[_ngcontent-%COMP%]:hover{color:#64748b}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-device[_ngcontent-%COMP%]:hover .device-icon[_ngcontent-%COMP%]{color:#64748b;opacity:1}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-time-divider[_ngcontent-%COMP%]{font-size:10px;color:#cbd5e1;line-height:1;-webkit-user-select:none;user-select:none;flex-shrink:0;transition:opacity .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .task-time[_ngcontent-%COMP%]{font-size:11.5px;color:#94a3b8;white-space:nowrap;flex-shrink:0;line-height:1;transition:opacity .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%]{position:absolute;right:0;top:50%;transform:translateY(-50%);width:20px;height:20px;padding:0;border:none;background:transparent;border-radius:4px;color:#94a3b8;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:all .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px;line-height:1}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%]:hover:not(:disabled){color:#ef4444;background:#fee2e2}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-delete-card[_ngcontent-%COMP%]:disabled{opacity:.3;cursor:not-allowed}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]{position:absolute;right:0;top:50%;transform:translateY(-50%);width:20px;height:20px;padding:0;border:none;background:transparent;border-radius:4px;color:#ef4444;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:all .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px;line-height:1}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]:hover:not(:disabled){color:#dc2626;background:#fee2e2;transform:translateY(-50%) scale(1.1)}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-header[_ngcontent-%COMP%] .task-card-header-actions[_ngcontent-%COMP%] .btn-stop-card[_ngcontent-%COMP%]:disabled{opacity:.3;cursor:not-allowed}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-goal[_ngcontent-%COMP%]{font-size:12.5px;font-weight:500;color:#1e293b;line-height:1.4;word-break:break-word;display:-webkit-box;-webkit-line-clamp:2;line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-footer[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center;gap:12px;border-top:1px solid rgba(241,245,249,.8);padding-top:6px;font-size:11.5px;color:#94a3b8}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-footer[_ngcontent-%COMP%] .task-id[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:11px;color:#94a3b8;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;flex:1;transition:color .15s ease}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-footer[_ngcontent-%COMP%] .task-id[_ngcontent-%COMP%]:hover{color:#475569}.chat-interface-container[_ngcontent-%COMP%] .chat-messages[_ngcontent-%COMP%] .task-card[_ngcontent-%COMP%] .task-card-footer[_ngcontent-%COMP%] .task-date[_ngcontent-%COMP%]{flex-shrink:0;font-size:11px;color:#94a3b8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:16px;height:100%}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:8px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .selector-title[_ngcontent-%COMP%]{font-size:11px;font-weight:700;color:#94a3b8;letter-spacing:.05em}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%]{display:flex;flex-wrap:wrap;gap:8px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid #e2e8f0;background:#fff;border-radius:20px;font-size:13px;font-weight:500;color:#475569;cursor:pointer;transition:all .15s ease}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%] .pill-icon[_ngcontent-%COMP%]{font-size:15px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill[_ngcontent-%COMP%]:hover{background:#f8fafc;border-color:#cbd5e1;color:#1e293b}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .notes-selector-container[_ngcontent-%COMP%] .notes-selector-list[_ngcontent-%COMP%] .note-selector-pill.active[_ngcontent-%COMP%]{background:#eff6ff;border-color:#bfdbfe;color:#1d4ed8;font-weight:600}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .empty-notes-container[_ngcontent-%COMP%]{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 24px;margin:auto 0;text-align:center;-webkit-user-select:none;user-select:none;animation:_ngcontent-%COMP%_fadeIn .25s cubic-bezier(.16,1,.3,1)}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .empty-notes-container[_ngcontent-%COMP%] .empty-notes-icon[_ngcontent-%COMP%]{width:52px;height:52px;border-radius:50%;background:#f1f5f9;border:1px solid #e2e8f0;display:flex;align-items:center;justify-content:center;margin-bottom:14px;box-shadow:0 1px 3px #00000005}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .empty-notes-container[_ngcontent-%COMP%] .empty-notes-icon[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:26px;color:#94a3b8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .empty-notes-container[_ngcontent-%COMP%] .empty-notes-title[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;font-weight:600;color:#1e293b;margin:0 0 6px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .empty-notes-container[_ngcontent-%COMP%] .empty-notes-subtitle[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:12.5px;color:#64748b;line-height:1.5;max-width:240px;margin:0}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%]{display:flex;flex-direction:column;flex:1;padding:16px 8px;overflow-y:auto}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .note-title[_ngcontent-%COMP%]{font-size:14px;font-weight:700;color:#1f1f1f;margin-top:0;margin-bottom:20px;font-family:Google Sans,Google Sans Text,Inter,sans-serif}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .code-segment[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;background-color:#f1f3f4;color:#1967d2;padding:2px 6px;border-radius:4px;font-size:14px;margin:0 2px;word-break:break-all;font-weight:400}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .checked[_ngcontent-%COMP%] .code-segment[_ngcontent-%COMP%]{color:#a1a1aa;background-color:#f8f9fa}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestones-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:6px;margin-bottom:24px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%]{display:flex;flex-direction:column}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block.checked[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.checked[_ngcontent-%COMP%]{opacity:.7}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block.checked[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%]{color:#a1a1aa}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:10px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon[_ngcontent-%COMP%]{font-size:20px;-webkit-user-select:none;user-select:none;margin-top:3px;font-variation-settings:"FILL" 1,"wght" 400,"GRAD" 0,"opsz" 24}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.checked[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.progress[_ngcontent-%COMP%]{color:#1a73e8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-status-icon.unchecked[_ngcontent-%COMP%]{color:#cbd5e1}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%]{font-family:Google Sans,Google Sans Text,Inter,sans-serif;color:#1f1f1f;font-size:14px;line-height:1.8;font-weight:400;word-break:break-word;flex:1}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-title-row[_ngcontent-%COMP%] .milestone-text[_ngcontent-%COMP%] .bold-segment[_ngcontent-%COMP%]{font-weight:inherit;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%]{margin-top:8px;display:flex;flex-direction:column;gap:10px;padding-left:28px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:10px;font-family:Google Sans,Google Sans Text,Inter,sans-serif;color:#1f1f1f;font-size:14px;line-height:1.8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.checked[_ngcontent-%COMP%]{color:#a1a1aa}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row.checked[_ngcontent-%COMP%] .check-icon.checked[_ngcontent-%COMP%]{opacity:.7}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{font-size:20px;-webkit-user-select:none;user-select:none;margin-top:3px;font-variation-settings:"FILL" 1,"wght" 400,"GRAD" 0,"opsz" 24}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.checked[_ngcontent-%COMP%], .chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.progress[_ngcontent-%COMP%]{color:#1a73e8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .check-icon.unchecked[_ngcontent-%COMP%]{color:#cbd5e1}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .bullet[_ngcontent-%COMP%]{color:#9ca3af;font-weight:700;-webkit-user-select:none;user-select:none;padding-left:2px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{word-break:break-word;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-substeps[_ngcontent-%COMP%] .substep-row[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%] .bold-segment[_ngcontent-%COMP%]{font-weight:inherit;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .milestone-block[_ngcontent-%COMP%] .milestone-checks-container[_ngcontent-%COMP%]{margin-top:8px;padding-left:28px;display:flex;flex-direction:column;gap:8px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%]{background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:8px 12px;display:flex;flex-direction:column;gap:4px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card.standalone[_ngcontent-%COMP%]{margin-top:8px;margin-bottom:8px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%]{display:flex;align-items:center}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag[_ngcontent-%COMP%]{font-family:Google Sans Text,Inter,-apple-system,sans-serif;font-size:10.5px;font-weight:600;line-height:1;letter-spacing:.6px;text-transform:uppercase;color:#64748b}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag.assert[_ngcontent-%COMP%]{color:#0284c7}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%]{font-family:inherit;font-size:13.5px;line-height:1.6;color:#334155;word-break:break-word}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .bold-segment[_ngcontent-%COMP%]{font-weight:600;color:#1e293b}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%] .code-segment[_ngcontent-%COMP%]{font-family:Fira Code,Roboto Mono,Consolas,monospace;font-size:12px;background:#f1f5f9;color:#0f172a;padding:1.5px 6px;border-radius:4px;border:1px solid #e2e8f0}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%]{background:#fffbeb;border-color:#fef3c7}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%] .card-header[_ngcontent-%COMP%] .card-tag[_ngcontent-%COMP%]{color:#b45309}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .verification-card.finding[_ngcontent-%COMP%] .card-body[_ngcontent-%COMP%]{color:#92400e}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;padding:8px 4px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%]{display:flex;align-items:flex-start;gap:10px;margin-bottom:14px;min-height:22px;font-family:Google Sans,Google Sans Text,Inter,sans-serif;color:#1f1f1f;font-size:14px;line-height:1.6}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.empty[_ngcontent-%COMP%]{height:12px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.h1[_ngcontent-%COMP%]{font-size:14px;font-weight:700;margin-top:24px;margin-bottom:12px;border-bottom:1px solid #e5e7eb;padding-bottom:6px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.h2[_ngcontent-%COMP%]{font-size:14px;font-weight:700;margin-top:20px;margin-bottom:10px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.h3[_ngcontent-%COMP%]{font-size:14px;font-weight:700;margin-top:14px;margin-bottom:8px}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.checked[_ngcontent-%COMP%]{color:#a1a1aa}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.checked[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{color:#1a73e8;opacity:.7}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.progress[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{color:#1a73e8}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.unchecked[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{color:#cbd5e1}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line.list-item[_ngcontent-%COMP%] .bullet[_ngcontent-%COMP%]{color:#9ca3af;font-weight:700;-webkit-user-select:none;user-select:none}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] .check-icon[_ngcontent-%COMP%]{font-size:20px;-webkit-user-select:none;user-select:none;margin-top:1px;font-variation-settings:"FILL" 1,"wght" 400,"GRAD" 0,"opsz" 24}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%]{word-break:break-word;color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:inherit}.chat-interface-container[_ngcontent-%COMP%] .notes-tab-content[_ngcontent-%COMP%] .note-viewer-cards[_ngcontent-%COMP%] .other-lines-container[_ngcontent-%COMP%] .md-line[_ngcontent-%COMP%] .line-text[_ngcontent-%COMP%] .bold-segment[_ngcontent-%COMP%]{font-weight:inherit;color:inherit}@keyframes _ngcontent-%COMP%_pulse{0%{transform:scale(.85);opacity:.6}50%{transform:scale(1.15);opacity:1}to{transform:scale(.85);opacity:.6}}@keyframes _ngcontent-%COMP%_bannerSlideIn{0%{transform:translateY(5px);opacity:0}to{transform:translateY(0);opacity:1}}@keyframes _ngcontent-%COMP%_fadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_pulse-black-dot-small{0%{box-shadow:0 0 #0006}70%{box-shadow:0 0 0 4px #0000}to{box-shadow:0 0 #0000}}@keyframes _ngcontent-%COMP%_pulse-blue-dot{0%{box-shadow:0 0 #2563eb66}70%{box-shadow:0 0 0 5px #2563eb00}to{box-shadow:0 0 #2563eb00}}@container (max-width: 380px){.chat-header[_ngcontent-%COMP%]{padding:0 8px;gap:6px;height:36px}.chat-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%]{display:none}.chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%]{padding:2px;gap:1px}.chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%]{padding:5px 8px;font-size:12px;gap:4px}.chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{font-size:14px}.chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%]{height:24px;width:30px;padding:0;font-size:12px;gap:0;border-radius:16px;box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center}.chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px;line-height:1}.chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%] .btn-text[_ngcontent-%COMP%]{display:none}}@container (max-width: 340px){.chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%]{padding:5px 6px}.chat-header[_ngcontent-%COMP%] .tab-selector-bar[_ngcontent-%COMP%] .tab-selector-btn[_ngcontent-%COMP%] .tab-text[_ngcontent-%COMP%]{display:none}.chat-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] .btn-clear-history[_ngcontent-%COMP%]{width:26px}}']})};var l4=["videoRef"],d4=["stepImgRef"],u4=["stepOverlayRef"];function g4(n,t){if(n&1){let e=L();j(0,"div",5),ue("click",function(){v(e);let i=m();return y(i.isTheaterMode.set(!1))}),H()}}function p4(n,t){if(n&1){let e=L();j(0,"div",12)(1,"button",18),ue("click",function(){v(e);let i=m(2);return y(i.agentService.setPlayerMode("video"))}),j(2,"span",19),u(3,"videocam"),H(),j(4,"span"),u(5,"Video"),H()(),j(6,"button",20),ue("click",function(){v(e);let i=m(2);return y(i.agentService.setPlayerMode("steps"))}),j(7,"span",19),u(8,"slideshow"),H(),j(9,"span"),u(10,"Steps"),H()()()}if(n&2){let e=m(2);g(),w("active",e.agentService.playerMode()==="video"),g(5),w("active",e.agentService.playerMode()==="steps")}}function f4(n,t){if(n&1){let e=L();j(0,"button",21),ue("click",function(){v(e);let i=m(2);return y(i.toggleTheater())}),j(1,"span",14),u(2),H()()}if(n&2){let e=m(2);je("title",e.isTheaterMode()?"Exit Theater":"Theater Mode"),g(2),O(e.isTheaterMode()?"close_fullscreen":"open_in_full")}}function m4(n,t){if(n&1){let e=L();j(0,"div",22)(1,"img",24),ue("error",function(){v(e);let i=m(4);return y(i.onLiveStreamError())}),H(),j(2,"div",25)(3,"div",26),yi(4,"span",27),j(5,"span"),u(6,"LIVE"),H()(),j(7,"button",28),ue("click",function(){v(e);let i=m(4);return y(i.retryLiveStream())}),j(8,"span",14),u(9,"refresh"),H()()()()}if(n&2){let e=m(4);g(),je("src",e.liveStreamUrl(),kt)}}function _4(n,t){if(n&1){let e=L();j(0,"div",23)(1,"div",29)(2,"span",30),u(3,"phonelink_off"),H()(),j(4,"h4",31),u(5,"Device Stream Connecting"),H(),j(6,"p",32),u(7,"Waiting for ADB device screen stream..."),H(),j(8,"button",33),ue("click",function(){v(e);let i=m(4);return y(i.retryLiveStream())}),j(9,"span",14),u(10,"refresh"),H(),u(11," Reconnect "),H()()}}function h4(n,t){if(n&1&&(M(0,m4,10,1,"div",22),M(1,_4,12,0,"div",23)),n&2){let e=m(3);b(e.liveStreamError()?-1:0),g(),b(e.liveStreamError()?1:-1)}}function C4(n,t){if(n&1&&(j(0,"div",34)(1,"span",36),u(2,"progress_activity"),H(),j(3,"span"),u(4),H()()),n&2){let e=m(4);g(4),O(e.agentService.recordingPlaybackMessage()||"Preparing screen recording...")}}function M4(n,t){n&1&&(j(0,"div",37)(1,"span",60),u(2,"info"),H(),j(3,"span"),u(4,"Screen recording unavailable. Replaying step-by-step screenshots."),H()())}function b4(n,t){n&1&&(j(0,"div",39)(1,"span",36),u(2,"progress_activity"),H(),j(3,"span"),u(4,"Loading screenshot..."),H()())}function P4(n,t){n&1&&(j(0,"div",40)(1,"span",61),u(2,"broken_image"),H(),j(3,"span"),u(4,"Screenshot preview unavailable for this step"),H()())}function O4(n,t){if(n&1){let e=L();j(0,"div",41)(1,"img",62,0),ue("load",function(){v(e);let i=m(5);return y(i.onStepImageLoad())})("error",function(){v(e);let i=m(5);return y(i.onStepImageError())})("click",function(){v(e);let i=m(5);return y(i.openImageInNewTab())}),H(),yi(3,"div",63,1),H()}if(n&2){let e,o=m(5);g(),je("src",o.currentStepImageUrl(),kt)("alt",(e=o.currentStepFrame())==null?null:e.title),g(2),Se("display",o.viewMode()==="post"?"none":"block")}}function v4(n,t){if(n&1&&(j(0,"span",68),u(1),H()),n&2){let e=m(),o=m(5);g(),V(" ",o.getStepActionIcon(e.action)," ")}}function y4(n,t){if(n&1){let e=L();j(0,"div",70)(1,"button",72),ue("click",function(i){v(e);let r=m(6);return y(r.setStepViewMode("pre",i))}),u(2," Pre "),H(),j(3,"button",73),ue("click",function(i){v(e);let r=m(6);return y(r.setStepViewMode("post",i))}),u(4," Post "),H()()}if(n&2){let e=m(6);g(),w("active",e.viewMode()==="pre"),g(2),w("active",e.viewMode()==="post")}}function x4(n,t){if(n&1){let e=L();j(0,"div",64),ue("click",function(i){return i.stopPropagation()}),j(1,"div",65)(2,"span",66),u(3),H(),j(4,"span",67),M(5,v4,2,1,"span",68),u(6),H()(),j(7,"div",69),M(8,y4,5,4,"div",70),j(9,"button",71),ue("click",function(i){v(e);let r=m(5);return y(r.toggleCardPin(i))}),j(10,"span",14),u(11),H()()()()}if(n&2){let e=t,o=m(5);w("visible",o.isCardVisible()),g(3),V("Step ",e.stepNumber),g(),je("title",e.actionText||e.title),g(),b(e.action?5:-1),g(),V(" ",e.actionText||e.title," "),g(2),b(e.postImageUrl?8:-1),g(),w("pinned",o.isCardPinned()),je("title",o.isCardPinned()?"Unpin details card (auto-hide on mouse leave)":"Pin details card (keep always visible)"),g(2),O(o.isCardPinned()?"push_pin":"keep")}}function w4(n,t){if(n&1){let e=L();j(0,"span",75),ue("click",function(i){let r=v(e).$index;return m(6).seekStepFrame(r),y(i.stopPropagation())}),H()}if(n&2){let e=t.$implicit,o=t.$index,i=m(6);Se("left",o/(i.totalStepFrames()-1)*100,"%"),w("active",o===i.activeStepIndex())("passed",o1?11:-1),g(3),je("title",o.isStepPlaying()?"Pause":"Play Slideshow"),g(2),O(o.isStepPlaying()?"pause":"play_arrow"),g(),je("disabled",o.activeStepIndex()<=0),g(3),je("disabled",o.activeStepIndex()>=o.totalStepFrames()-1),g(7),We(" Step ",o.activeStepIndex()+1," / ",o.totalStepFrames()," "),g(2),je("title","Speed: "+o.playbackRate()+"x (Click to cycle)"),g(2),V("",o.playbackRate(),"x"),g(),w("active",o.isLooping()),je("title",o.isLooping()?"Loop enabled":"Enable loop")}}function E4(n,t){if(n&1){let e=L();j(0,"div",76)(1,"video",77,2),ue("loadedmetadata",function(){v(e);let i=m(5);return y(i.onLoadedMetadata())})("timeupdate",function(){v(e);let i=m(5);return y(i.onTimeUpdate())})("play",function(){v(e);let i=m(5);return y(i.onPlay())})("pause",function(){v(e);let i=m(5);return y(i.onPause())})("ended",function(){v(e);let i=m(5);return y(i.onEnded())})("error",function(){v(e);let i=m(5);return y(i.onError())})("click",function(){v(e);let i=m(5);return y(i.togglePlay())}),H()()}if(n&2){let e=m(5);g(),je("src",e.currentVideoUrl(),kt)}}function D4(n,t){if(n&1){let e=L();j(0,"div",43)(1,"div",44)(2,"input",45),ue("input",function(i){v(e);let r=m(5);return y(r.onScrub(i))}),H(),yi(3,"div",46),H(),j(4,"div",48)(5,"div",49)(6,"button",50),ue("click",function(){v(e);let i=m(5);return y(i.togglePlay())}),j(7,"span",14),u(8),H()(),j(9,"button",53),ue("click",function(){v(e);let i=m(5);return y(i.restart())}),j(10,"span",14),u(11,"replay"),H()(),j(12,"span",54),u(13),H()(),j(14,"div",55)(15,"button",56),ue("click",function(){v(e);let i=m(5);return y(i.cycleSpeed())}),j(16,"span",57),u(17),H()(),j(18,"button",58),ue("click",function(){v(e);let i=m(5);return y(i.toggleLoop())}),j(19,"span",14),u(20,"repeat"),H()(),j(21,"button",58),ue("click",function(){v(e);let i=m(5);return y(i.toggleMute())}),j(22,"span",14),u(23),H()(),j(24,"button",78),ue("click",function(){v(e);let i=m(5);return y(i.openInNewTab())}),j(25,"span",14),u(26,"open_in_new"),H()()()()()}if(n&2){let e=m(5);g(2),je("min",0)("max",e.duration()||100)("step",.1)("value",e.currentTime()),g(),Se("width",e.progressPercent(),"%"),g(3),je("title",e.isPlaying()?"Pause":"Play"),g(2),O(e.isPlaying()?"pause":"play_arrow"),g(5),We(" ",e.formatTime(e.currentTime())," / ",e.formatTime(e.duration())," "),g(2),je("title","Speed: "+e.playbackRate()+"x (Click to cycle)"),g(2),V("",e.playbackRate(),"x"),g(),w("active",e.isLooping()),je("title",e.isLooping()?"Loop enabled":"Enable loop"),g(3),je("title",e.isMuted()?"Unmute":"Mute"),g(2),O(e.isMuted()?"volume_off":"volume_up")}}function I4(n,t){if(n&1&&(M(0,E4,3,1,"div",76),M(1,D4,27,17,"div",43)),n&2){let e=m(4);b(e.videoLoadError()?-1:0),g(),b(e.videoLoadError()?-1:1)}}function T4(n,t){if(n&1){let e=L();j(0,"button",33),ue("click",function(){v(e);let i=m(5);return i.videoLoadError.set(!1),y(i.agentService.retryVideoRecording())}),j(1,"span",14),u(2,"refresh"),H(),u(3," Retry "),H()}}function A4(n,t){if(n&1&&(j(0,"div",23)(1,"div",29)(2,"span",30),u(3,"videocam_off"),H()(),j(4,"h4",31),u(5),H(),j(6,"p",32),u(7),H(),M(8,T4,4,0,"button",79),H()),n&2){let e=m(4);g(5),V(" ",e.agentService.recordingPlaybackStatus()==="failed"||e.videoLoadError()?"Recording Playback Failed":"No Video Recording Available"," "),g(2),V(" ",e.videoLoadError()?"The finalized recording could not be played.":e.agentService.recordingPlaybackMessage()||"No screen recording was found for this task."," "),g(),b(e.agentService.recordingPlaybackStatus()==="failed"||e.videoLoadError()?8:-1)}}function N4(n,t){if(n&1&&(M(0,C4,5,1,"div",34),M(1,k4,38,23,"div",35),M(2,I4,2,2),M(3,A4,9,3,"div",23)),n&2){let e,o=m(3);b(o.agentService.playerMode()==="video"&&(o.agentService.recordingPlaybackStatus()==="processing"||o.agentService.isVideoLoading())?0:-1),g(),b(o.agentService.playerMode()==="steps"&&o.totalStepFrames()>0?1:-1),g(),b((e=o.agentService.playerMode()==="video"&&o.agentService.recordingPlaybackStatus()==="ready"&&!o.agentService.isVideoLoading()&&o.agentService.activeVideoUrl())?2:-1,e),g(),b(!o.agentService.isVideoLoading()&&(o.agentService.playerMode()==="video"||o.totalStepFrames()===0)&&(o.agentService.recordingPlaybackStatus()==="failed"||o.agentService.recordingPlaybackStatus()==="unavailable"||o.videoLoadError())&&o.totalStepFrames()===0?3:-1)}}function R4(n,t){if(n&1&&(j(0,"div",17),M(1,h4,2,2),M(2,N4,4,4),H()),n&2){let e=m(2);g(),b(e.agentService.recordingPlaybackStatus()==="live"?1:-1),g(),b(e.agentService.recordingPlaybackStatus()!=="live"?2:-1)}}function F4(n,t){if(n&1){let e=L();j(0,"div",6)(1,"header",7),ue("mousedown",function(i){v(e);let r=m();return y(r.startDrag(i))}),j(2,"div",8)(3,"span",9),u(4),H(),j(5,"span",10),u(6),H()(),j(7,"div",11),ue("mousedown",function(i){return i.stopPropagation()}),M(8,p4,11,4,"div",12),j(9,"button",13),ue("click",function(){v(e);let i=m();return y(i.toggleMinimize())}),j(10,"span",14),u(11),H()(),M(12,f4,3,2,"button",15),j(13,"button",16),ue("click",function(){v(e);let i=m();return y(i.agentService.closeVideoPlayer())}),j(14,"span",14),u(15,"close"),H()()()(),M(16,R4,3,2,"div",17),H()}if(n&2){let e=m();Se("left",e.isTheaterMode()?null:e.posX(),"px")("top",e.isTheaterMode()?null:e.posY(),"px"),w("minimized",e.agentService.isVideoMinimized())("theater",e.isTheaterMode()),g(4),V(" ",e.agentService.recordingPlaybackStatus()==="live"?"sensors":e.agentService.playerMode()==="steps"?"slideshow":e.agentService.recordingPlaybackStatus()==="processing"?"progress_activity":"smart_display"," "),g(),je("title",e.agentService.activeVideoTitle()),g(),V(" ",e.agentService.recordingPlaybackStatus()==="live"?"Live Device Screen":e.agentService.playerMode()==="steps"?"Step Replay ("+(e.totalStepFrames()>0?e.activeStepIndex()+1+"/"+e.totalStepFrames():"0")+")":e.agentService.recordingPlaybackStatus()==="processing"?"Preparing Recording":e.agentService.activeVideoTitle()||"Screen Recording"," "),g(2),b(e.agentService.recordingPlaybackStatus()==="ready"&&e.agentService.hasCurrentSessionStepFrames()&&!e.agentService.isVideoMinimized()?8:-1),g(),je("title",e.agentService.isVideoMinimized()?"Restore":"Minimize"),g(2),O(e.agentService.isVideoMinimized()?"expand_less":"remove"),g(),b(e.agentService.isVideoMinimized()?-1:12),g(4),b(e.agentService.isVideoMinimized()?-1:16)}}var Kd=class n{agentService=D(It);zone=D(ge);videoRef;stepImgRef;stepOverlayRef;isPlaying=x(!1);playbackRate=x(1);isLooping=x(!1);currentTime=x(0);duration=x(0);isMuted=x(!1);isTheaterMode=x(!1);videoLoadError=x(!1);liveStreamUrl=x("/api/stream/device-live");liveStreamError=x(!1);activeSegmentIndex=x(0);currentVideoUrl=I(()=>this.agentService.activeVideoSegments()[this.activeSegmentIndex()]?.url||this.agentService.activeVideoUrl());pendingLocalTime=null;pendingAutoplay=!1;pendingAbsoluteSeek=null;activeStepIndex=x(0);isStepPlaying=x(!1);viewMode=x("pre");isCardHovered=x(!1);isCardPinned=x(!1);isCardVisible=I(()=>this.isCardHovered()||this.isCardPinned());isStepImageLoading=x(!1);stepImageError=x(!1);stepTimer=null;stepFrames=I(()=>this.agentService.currentSessionStepFrames());totalStepFrames=I(()=>this.stepFrames().length);currentStepFrame=I(()=>{let t=this.stepFrames(),e=this.activeStepIndex();return t[e]||null});currentStepImageUrl=I(()=>{let t=this.currentStepFrame();return t?this.viewMode()==="post"&&t.postImageUrl?t.postImageUrl:t.preImageUrl||t.imageUrl||"":""});stepProgressPercent=I(()=>{let t=this.totalStepFrames();return t<=1?0:this.activeStepIndex()/(t-1)*100});speedOptions=[.5,1,1.5,2,4];posX=x(typeof window<"u"?Math.max(20,window.innerWidth-420):100);posY=x(typeof window<"u"?Math.max(60,window.innerHeight-700):100);isDragging=!1;dragStartX=0;dragStartY=0;initialPosX=0;initialPosY=0;constructor(){Ye(()=>{let e=this.agentService.activeVideoUrl(),o=this.agentService.activeVideoSegments();this.videoLoadError.set(!1),this.activeSegmentIndex.set(0),this.currentTime.set(0),this.duration.set(o.reduce((i,r)=>i+r.duration,0)),this.isPlaying.set(!1)},{allowSignalWrites:!0});let t=null;Ye(()=>{let e=this.agentService.currentSessionId(),o=this.totalStepFrames();e!==t?(t=e,this.activeStepIndex.set(0),this.viewMode.set("pre"),this.pauseStepPlay()):o>0&&this.activeStepIndex()>=o&&this.activeStepIndex.set(o-1)},{allowSignalWrites:!0}),Ye(()=>{this.currentStepImageUrl()?(this.isStepImageLoading.set(!0),this.stepImageError.set(!1)):this.isStepImageLoading.set(!1)},{allowSignalWrites:!0}),Ye(()=>{this.activeStepIndex(),this.viewMode(),setTimeout(()=>this.drawStepOverlay(),40)}),Ye(()=>{let e=this.agentService.videoSeekRequest();if(!e||(this.pendingAbsoluteSeek=e.seconds,this.agentService.recordingPlaybackStatus()!=="ready"))return;let o=this.videoRef?.nativeElement;if(o&&o.readyState>=1){let i=this.pendingAbsoluteSeek;this.pendingAbsoluteSeek=null,this.seek(i,!0)}}),Ye(()=>{let e=this.agentService.stepSeekRequest();e&&this.seekStepFrame(e.index)})}onLiveStreamError(){this.liveStreamError.set(!0)}retryLiveStream(){this.liveStreamError.set(!1),this.liveStreamUrl.set(`/api/stream/device-live?t=${Date.now()}`)}formatTime(t){if(isNaN(t)||t<0)return"00:00";let e=Math.floor(t/60),o=Math.floor(t%60),i=e<10?"0"+e:String(e),r=o<10?"0"+o:String(o);return`${i}:${r}`}progressPercent=I(()=>{let t=this.duration();return t<=0?0:Math.min(100,Math.max(0,this.currentTime()/t*100))});startDrag(t){this.isTheaterMode()||(this.isDragging=!0,this.dragStartX=t.clientX,this.dragStartY=t.clientY,this.initialPosX=this.posX(),this.initialPosY=this.posY(),t.preventDefault(),this.zone.runOutsideAngular(()=>{document.addEventListener("mousemove",this.onDrag),document.addEventListener("mouseup",this.stopDrag)}))}onDrag=t=>{if(!this.isDragging)return;let e=t.clientX-this.dragStartX,o=t.clientY-this.dragStartY,i=10,r=10,a=Math.max(10,window.innerWidth-240),s=Math.max(10,window.innerHeight-80),c=Math.max(i,Math.min(a,this.initialPosX+e)),p=Math.max(r,Math.min(s,this.initialPosY+o));this.posX.set(c),this.posY.set(p)};stopDrag=()=>{this.isDragging=!1,document.removeEventListener("mousemove",this.onDrag),document.removeEventListener("mouseup",this.stopDrag)};onResize(){let t=Math.max(10,window.innerWidth-240),e=Math.max(10,window.innerHeight-80);this.posX()>t&&this.posX.set(t),this.posY()>e&&this.posY.set(e)}onKeyDown(t){this.agentService.isVideoWindowOpen()&&t.key==="Escape"&&(this.isTheaterMode()?this.isTheaterMode.set(!1):this.agentService.closeVideoPlayer())}onLoadedMetadata(){let t=this.videoRef?.nativeElement;if(t){let e=this.agentService.activeVideoSegments();if(e.length||this.duration.set(t.duration||0),this.videoLoadError.set(!1),t.playbackRate=this.playbackRate(),t.loop=this.isLooping()&&!e.length,t.muted=this.isMuted(),this.pendingAbsoluteSeek!==null){let o=this.pendingAbsoluteSeek;if(this.pendingAbsoluteSeek=null,this.seek(o,!0),this.pendingLocalTime!==null)return}this.pendingLocalTime!==null&&(t.currentTime=Math.max(0,Math.min(t.duration||this.pendingLocalTime,this.pendingLocalTime)),this.pendingLocalTime=null),this.pendingAutoplay&&(this.pendingAutoplay=!1,t.play().catch(()=>{})),this.agentService.consumeVideoAutoplay()&&(this.isMuted.set(!0),t.muted=!0,t.play().catch(()=>{this.isPlaying.set(!1)}))}}onTimeUpdate(){let t=this.videoRef?.nativeElement;if(t){let e=this.agentService.activeVideoSegments(),o=e[this.activeSegmentIndex()];this.currentTime.set((o?.start||0)+t.currentTime),!e.length&&t.duration&&t.duration!==this.duration()&&this.duration.set(t.duration)}}onPlay(){this.isPlaying.set(!0)}onPause(){this.isPlaying.set(!1)}onEnded(){let t=this.agentService.activeVideoSegments();if(t.length&&this.activeSegmentIndex()e+1);return}if(t.length&&this.isLooping()){this.seek(0,!0);return}this.isPlaying.set(!1)}onError(){this.videoLoadError.set(!0),this.isPlaying.set(!1)}togglePlay(){let t=this.videoRef?.nativeElement;t&&(t.paused?t.play().catch(()=>{}):t.pause())}restart(){this.seek(0,!0)}seek(t,e=!1){let o=this.videoRef?.nativeElement;if(!o)return;let i=Math.max(0,Math.min(this.duration(),t)),r=this.agentService.activeVideoSegments();if(!r.length){o.currentTime=i,e&&o.play().catch(()=>{});return}let a=r.findIndex(c=>i{})):(this.pendingLocalTime=s,this.pendingAutoplay=e||!o.paused,this.activeSegmentIndex.set(a)),this.currentTime.set(i)}onScrub(t){let e=t.target,o=parseFloat(e.value);this.seek(o)}setSpeed(t){this.playbackRate.set(t);let e=this.videoRef?.nativeElement;e&&(e.playbackRate=t),this.isStepPlaying()&&this.scheduleNextStepFrame()}toggleLoop(){let t=!this.isLooping();this.isLooping.set(t);let e=this.videoRef?.nativeElement;e&&(e.loop=t&&!this.agentService.activeVideoSegments().length)}toggleMute(){let t=!this.isMuted();this.isMuted.set(t);let e=this.videoRef?.nativeElement;e&&(e.muted=t)}toggleTheater(){this.isTheaterMode.set(!this.isTheaterMode())}toggleMinimize(){this.agentService.isVideoMinimized.set(!this.agentService.isVideoMinimized())}openInNewTab(){let t=this.agentService.activeVideoUrl();t&&window.open(t,"_blank")}toggleStepPlay(){this.isStepPlaying()?this.pauseStepPlay():this.startStepPlay()}startStepPlay(){this.totalStepFrames()<=0||(this.activeStepIndex()>=this.totalStepFrames()-1&&this.activeStepIndex.set(0),this.isStepPlaying.set(!0),this.scheduleNextStepFrame())}pauseStepPlay(){this.isStepPlaying.set(!1),this.stepTimer&&(clearTimeout(this.stepTimer),this.stepTimer=null)}scheduleNextStepFrame(){this.stepTimer&&clearTimeout(this.stepTimer);let t=Math.max(300,1500/this.playbackRate());this.stepTimer=setTimeout(()=>{this.isStepPlaying()&&(this.activeStepIndex()e+1),this.scheduleNextStepFrame()):this.isLooping()?(this.activeStepIndex.set(0),this.scheduleNextStepFrame()):this.pauseStepPlay())},t)}prevStepFrame(){this.pauseStepPlay(),this.viewMode.set("pre"),this.activeStepIndex.update(t=>Math.max(0,t-1))}nextStepFrame(){this.pauseStepPlay(),this.viewMode.set("pre"),this.activeStepIndex.update(t=>Math.min(this.totalStepFrames()-1,t+1))}seekStepFrame(t){this.viewMode.set("pre"),this.activeStepIndex.set(Math.max(0,Math.min(this.totalStepFrames()-1,t)))}onStepScrub(t){let e=t.target;this.seekStepFrame(parseInt(e.value,10))}restartStep(){this.seekStepFrame(0),this.startStepPlay()}setStepViewMode(t,e){e&&e.stopPropagation(),this.viewMode.set(t),setTimeout(()=>this.drawStepOverlay(),40)}toggleStepViewMode(t){t&&t.stopPropagation(),this.setStepViewMode(this.viewMode()==="pre"?"post":"pre")}onViewportMouseEnter(){this.isCardHovered.set(!0)}onViewportMouseLeave(){this.isCardHovered.set(!1)}toggleCardPin(t){t&&t.stopPropagation(),this.isCardPinned.update(e=>!e)}cycleSpeed(){let t=this.playbackRate(),o=(this.speedOptions.indexOf(t)+1)%this.speedOptions.length;this.setSpeed(this.speedOptions[o])}onStepImageLoad(){this.isStepImageLoading.set(!1),this.stepImageError.set(!1),this.drawStepOverlay()}onStepImageError(){this.isStepImageLoading.set(!1),this.stepImageError.set(!0)}drawStepOverlay(){let t=this.stepImgRef?.nativeElement,e=this.stepOverlayRef?.nativeElement;if(!t||!e||(e.innerHTML="",this.viewMode()==="post"))return;let o=this.currentStepFrame();if(!(!o||!o.action))try{Rd(t,e,o.action)}catch(i){console.warn("Failed to draw step coordinates overlay:",i)}}getStepActionIcon(t){return zd(t)}openImageInNewTab(){let t=this.currentStepImageUrl()||this.currentStepFrame()?.imageUrl;t&&window.open(t,"_blank")}trackStepFrame(t,e){return e.stepId||String(t)}ngOnDestroy(){this.stepTimer&&(clearTimeout(this.stepTimer),this.stepTimer=null),document.removeEventListener("mousemove",this.onDrag),document.removeEventListener("mouseup",this.stopDrag)}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-floating-video-player"]],viewQuery:function(e,o){if(e&1&&fr(l4,5)(d4,5)(u4,5),e&2){let i;eo(i=to())&&(o.videoRef=i.first),eo(i=to())&&(o.stepImgRef=i.first),eo(i=to())&&(o.stepOverlayRef=i.first)}},hostBindings:function(e,o){e&1&&k("resize",function(){return o.onResize()},_l)("keydown",function(r){return o.onKeyDown(r)},_l)},decls:2,vars:2,consts:[["stepImgRef",""],["stepOverlayRef",""],["videoRef",""],[1,"theater-backdrop"],[1,"floating-video-wrapper",3,"minimized","theater","left","top"],[1,"theater-backdrop",3,"click"],[1,"floating-video-wrapper"],[1,"window-header",3,"mousedown"],[1,"header-left"],[1,"material-symbols-outlined","header-icon"],[1,"header-title",3,"title"],[1,"header-actions",3,"mousedown"],[1,"mode-toggle-group"],["aria-label","Minimize",1,"header-btn",3,"click","title"],[1,"material-symbols-outlined"],["aria-label","Theater",1,"header-btn",3,"title"],["title","Close","aria-label","Close",1,"header-btn","close-btn",3,"click"],[1,"video-body"],["title","Video Recording",1,"mode-btn",3,"click"],[1,"material-symbols-outlined","mode-icon"],["title","Step Screenshot Replay",1,"mode-btn",3,"click"],["aria-label","Theater",1,"header-btn",3,"click","title"],[1,"live-stream-container"],[1,"empty-video-fallback"],["alt","Live Device Screen",1,"live-screen-img",3,"error","src"],[1,"live-status-overlay"],[1,"live-badge"],[1,"pulse-dot"],["title","Refresh Live Stream",1,"live-refresh-btn",3,"click"],[1,"fallback-icon-wrap"],[1,"material-symbols-outlined","fallback-icon"],[1,"fallback-title"],[1,"fallback-desc"],[1,"retry-btn",3,"click"],[1,"video-loading-state"],[1,"step-replay-container"],[1,"material-symbols-outlined","spinning-icon"],[1,"step-replay-notice"],[1,"step-image-viewport",3,"mouseenter","mouseleave"],[1,"step-image-loading-state"],[1,"step-image-error-state"],[1,"step-image-frame-wrap"],[1,"step-frame-badge",3,"visible"],[1,"video-controls-footer"],[1,"scrubber-bar-container"],["type","range",1,"scrubber-range",3,"input","min","max","step","value"],[1,"scrubber-progress-fill"],[1,"step-ticks-wrapper"],[1,"controls-row"],[1,"controls-left"],[1,"ctrl-btn","play-btn",3,"click","title"],["title","Previous Step",1,"ctrl-btn",3,"click","disabled"],["title","Next Step",1,"ctrl-btn",3,"click","disabled"],["title","Replay from start",1,"ctrl-btn",3,"click"],[1,"time-display"],[1,"controls-right"],[1,"ctrl-btn","speed-pill-btn",3,"click","title"],[1,"speed-label"],[1,"ctrl-btn",3,"click","title"],["title","Open image in new tab",1,"ctrl-btn",3,"click"],[1,"material-symbols-outlined","notice-icon"],[1,"material-symbols-outlined","broken-icon"],["title","Click to view image in new tab",1,"step-frame-img",3,"load","error","click","src","alt"],[1,"overlay-layer"],[1,"step-frame-badge",3,"click"],[1,"badge-content-left"],[1,"badge-step-no"],[1,"badge-action-desc",3,"title"],[1,"material-symbols-outlined","action-type-icon"],[1,"badge-actions-right"],[1,"pre-post-toggle"],[1,"pin-card-btn",3,"click","title"],["title","View screen before action (with touch coordinates)",1,"toggle-btn",3,"click"],["title","View resulting screen after action executed",1,"toggle-btn",3,"click"],[1,"step-tick",3,"active","passed","left","title"],[1,"step-tick",3,"click","title"],[1,"video-container"],["playsinline","",3,"loadedmetadata","timeupdate","play","pause","ended","error","click","src"],["title","Open video in new tab",1,"ctrl-btn",3,"click"],[1,"retry-btn"]],template:function(e,o){e&1&&(M(0,g4,1,0,"div",3),M(1,F4,17,16,"div",4)),e&2&&(b(o.agentService.isVideoWindowOpen()&&o.isTheaterMode()?0:-1),g(),b(o.agentService.isVideoWindowOpen()?1:-1))},dependencies:[Qt],styles:['.theater-backdrop[_ngcontent-%COMP%]{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#0f172a99;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);z-index:1040;animation:_ngcontent-%COMP%_fadeIn .15s ease-out}.floating-video-wrapper[_ngcontent-%COMP%]{position:fixed;width:400px;max-width:calc(100vw - 32px);background:#ffffffd6;backdrop-filter:blur(24px) saturate(180%);-webkit-backdrop-filter:blur(24px) saturate(180%);border:1px solid rgba(255,255,255,.85);border-radius:16px;box-shadow:0 20px 50px -10px #0f172a38,0 0 0 1px #e2e8f099,inset 0 1px 1px #ffffffe6;z-index:1050;overflow:hidden;display:flex;flex-direction:column;box-sizing:border-box;animation:_ngcontent-%COMP%_popIn .2s cubic-bezier(.16,1,.3,1);-webkit-user-select:none;user-select:none;font-family:Google Sans,Google Sans Text,Inter,system-ui,-apple-system,sans-serif}.floating-video-wrapper.minimized[_ngcontent-%COMP%]{width:280px;box-shadow:0 10px 25px -5px #0f172a2e}.floating-video-wrapper.minimized[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%]{border-bottom:none}.floating-video-wrapper.theater[_ngcontent-%COMP%]{left:50%!important;top:50%!important;transform:translate(-50%,-50%);width:85vw;max-width:960px;height:auto;max-height:88vh;box-shadow:0 24px 60px -12px #0f172a59}.floating-video-wrapper.theater[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%]{max-height:calc(88vh - 46px)}.floating-video-wrapper.theater[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-container[_ngcontent-%COMP%] video[_ngcontent-%COMP%]{max-height:calc(88vh - 120px)}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%]{height:44px;background:#ffffffbf;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border-bottom:1px solid rgba(226,232,240,.75);display:flex;align-items:center;justify-content:space-between;padding:0 8px 0 12px;cursor:grab;gap:8px;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%]:active{cursor:grabbing}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px;overflow:hidden;flex:1}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .header-icon[_ngcontent-%COMP%]{font-size:18px;color:#2563eb;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%]{font-size:13px;font-weight:600;color:#0f172a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;letter-spacing:-.2px}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%]{display:flex;align-items:center;background:#f1f5f9a6;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.8);border-radius:8px;padding:2px;margin-left:4px;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%]{border:none;background:transparent;color:#475569;font-size:10px;font-weight:700;padding:2px 7px;border-radius:6px;cursor:pointer;transition:all .15s ease;display:flex;align-items:center;gap:4px}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%] .pulse-dot-mini[_ngcontent-%COMP%]{width:5px;height:5px;border-radius:50%;background:#ef4444;animation:_ngcontent-%COMP%_pulseRed 1.2s infinite}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%]:hover:not(:disabled){color:#0f172a;background:#fff9}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%] .mode-btn.active[_ngcontent-%COMP%]{background:#0284c7;color:#fff;font-weight:700;box-shadow:0 1px 3px #0284c74d}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] .mode-switcher-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%]:disabled{opacity:.4;cursor:not-allowed}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%]{display:flex;align-items:center;gap:4px;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .mode-toggle-group[_ngcontent-%COMP%]{display:flex;align-items:center;background:#f1f5f9b3;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.85);border-radius:8px;padding:2px;margin-right:4px;gap:2px}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .mode-toggle-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:3px;border:none;background:transparent;color:#475569;font-size:11px;font-weight:600;padding:2px 7px;border-radius:6px;cursor:pointer;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .mode-toggle-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%] .mode-icon[_ngcontent-%COMP%]{font-size:13px}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .mode-toggle-group[_ngcontent-%COMP%] .mode-btn[_ngcontent-%COMP%]:hover:not(.active){color:#0f172a;background:#fffc}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .mode-toggle-group[_ngcontent-%COMP%] .mode-btn.active[_ngcontent-%COMP%]{background:#0284c7;color:#fff;box-shadow:0 1px 3px #0284c74d}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .speed-pill-group[_ngcontent-%COMP%]{display:flex;align-items:center;background:#f1f5f9a6;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.8);border-radius:8px;padding:2px;margin-right:4px}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .speed-pill-group[_ngcontent-%COMP%] .speed-btn[_ngcontent-%COMP%]{border:none;background:transparent;color:#334155;font-size:11px;font-weight:700;padding:2px 6px;border-radius:4px;cursor:pointer;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .speed-pill-group[_ngcontent-%COMP%] .speed-btn[_ngcontent-%COMP%]:hover{color:#0f172a;background:#cbd5e1}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .speed-pill-group[_ngcontent-%COMP%] .speed-btn.active[_ngcontent-%COMP%]{background:#0284c7;color:#fff;font-weight:700;box-shadow:0 1px 3px #0284c74d}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .header-btn[_ngcontent-%COMP%]{background:#f1f5f9bf;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.85);color:#1e293b;cursor:pointer;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:7px;box-shadow:0 1px 3px #0f172a0a,inset 0 1px 1px #fffc;transition:all .2s cubic-bezier(.4,0,.2,1)}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .header-btn[_ngcontent-%COMP%]:hover{background:#fffffff2;color:#0f172a;border-color:#cbd5e1f2;transform:translateY(-.5px);box-shadow:0 2px 6px #0f172a14}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .header-btn.close-btn[_ngcontent-%COMP%]:hover{background:#fee2e2f2;color:#dc2626;border-color:#fca5a5e6;box-shadow:0 2px 6px #dc262626}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .header-btn[_ngcontent-%COMP%]:active{transform:scale(.96)}.floating-video-wrapper[_ngcontent-%COMP%] .window-header[_ngcontent-%COMP%] .header-actions[_ngcontent-%COMP%] .header-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:17px;font-weight:600}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%]{position:relative;background:#fff;display:flex;flex-direction:column;align-items:stretch;justify-content:flex-start;overflow:hidden}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-loading-state[_ngcontent-%COMP%]{padding:36px 20px;display:flex;flex-direction:column;align-items:center;gap:10px;background:#fff;width:100%;box-sizing:border-box;color:#475569;font-size:13px;font-weight:500}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-loading-state[_ngcontent-%COMP%] .spinning-icon[_ngcontent-%COMP%]{font-size:28px;color:#2563eb;animation:_ngcontent-%COMP%_spin 1s linear infinite}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%]{position:relative;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#fff;min-height:260px;overflow:hidden}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-screen-img[_ngcontent-%COMP%]{width:100%;max-height:520px;display:block;object-fit:contain;-webkit-user-select:none;user-select:none;background:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%]{position:absolute;top:10px;left:12px;right:12px;display:flex;align-items:center;justify-content:space-between;pointer-events:none}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%] .live-badge[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;background:#fef2f2;border:1px solid #fca5a5;color:#dc2626;font-size:10.5px;font-weight:700;padding:3px 9px;border-radius:12px;letter-spacing:.5px;box-shadow:0 1px 4px #dc262626}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%] .live-badge[_ngcontent-%COMP%] .pulse-dot[_ngcontent-%COMP%]{width:6px;height:6px;border-radius:50%;background:#ef4444;animation:_ngcontent-%COMP%_pulseRed 1.2s infinite}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%] .live-refresh-btn[_ngcontent-%COMP%]{pointer-events:auto;background:#fffffff2;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);border:1px solid #cbd5e1;color:#1e293b;cursor:pointer;display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;box-shadow:0 1px 3px #00000014;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%] .live-refresh-btn[_ngcontent-%COMP%]:hover{background:#f1f5f9;color:#0f172a;border-color:#94a3b8;transform:rotate(90deg)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .live-stream-container[_ngcontent-%COMP%] .live-status-overlay[_ngcontent-%COMP%] .live-refresh-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:16px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;background:#fff;position:relative;width:100%;overflow:hidden}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-replay-notice[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;background:#fffbeb;color:#b45309;padding:6px 12px;font-size:11.5px;font-weight:500;border-bottom:1px solid #fde68a}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-replay-notice[_ngcontent-%COMP%] .notice-icon[_ngcontent-%COMP%]{font-size:14px;color:#d97706;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;position:relative;background:#f8fafc;min-height:280px;max-height:520px;overflow:hidden;padding:10px 0}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-loading-state[_ngcontent-%COMP%], .floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-error-state[_ngcontent-%COMP%]{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;background:#f8fafc;color:#64748b;font-size:12px;font-weight:500;z-index:5}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-loading-state[_ngcontent-%COMP%] .spinning-icon[_ngcontent-%COMP%], .floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-error-state[_ngcontent-%COMP%] .spinning-icon[_ngcontent-%COMP%]{font-size:24px;color:#0284c7;animation:_ngcontent-%COMP%_spin 1s linear infinite}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-loading-state[_ngcontent-%COMP%] .broken-icon[_ngcontent-%COMP%], .floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-error-state[_ngcontent-%COMP%] .broken-icon[_ngcontent-%COMP%]{font-size:28px;color:#94a3b8}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-frame-wrap[_ngcontent-%COMP%]{position:relative;display:inline-flex;align-items:center;justify-content:center;max-width:92%;max-height:500px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-frame-wrap[_ngcontent-%COMP%] .step-frame-img[_ngcontent-%COMP%]{max-width:100%;max-height:500px;width:auto;height:auto;object-fit:contain;display:block;-webkit-user-select:none;user-select:none;cursor:pointer;border-radius:8px;box-shadow:0 4px 20px -2px #0f172a24,0 0 0 1px #e2e8f0e6;background:#fff;transition:opacity .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-frame-wrap[_ngcontent-%COMP%] .step-frame-img[_ngcontent-%COMP%]:hover{opacity:.98}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-image-frame-wrap[_ngcontent-%COMP%] .overlay-layer[_ngcontent-%COMP%]{position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:8}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%]:hover .step-frame-badge[_ngcontent-%COMP%]{opacity:1;transform:translateY(0);pointer-events:auto}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%]{position:absolute;bottom:12px;left:10px;right:10px;display:flex;align-items:center;justify-content:space-between;gap:8px;background:#0f172ae0;backdrop-filter:blur(14px) saturate(180%);-webkit-backdrop-filter:blur(14px) saturate(180%);border:1px solid rgba(255,255,255,.2);border-radius:10px;padding:6px 10px;color:#fff;box-shadow:0 8px 24px #00000080;opacity:0;transform:translateY(8px);pointer-events:none;transition:opacity .22s cubic-bezier(.16,1,.3,1),transform .22s cubic-bezier(.16,1,.3,1);z-index:15}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge.visible[_ngcontent-%COMP%]{opacity:1;transform:translateY(0);pointer-events:auto}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-content-left[_ngcontent-%COMP%]{display:flex;align-items:center;gap:7px;overflow:hidden;flex:1}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-content-left[_ngcontent-%COMP%] .badge-step-no[_ngcontent-%COMP%]{font-size:10.5px;font-weight:700;background:#0284c7;padding:2px 7px;border-radius:5px;letter-spacing:.3px;flex-shrink:0;box-shadow:0 1px 3px #0284c766}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-content-left[_ngcontent-%COMP%] .badge-action-desc[_ngcontent-%COMP%]{font-size:11.5px;font-weight:500;color:#f8fafc;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:4px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-content-left[_ngcontent-%COMP%] .badge-action-desc[_ngcontent-%COMP%] .action-type-icon[_ngcontent-%COMP%]{font-size:15px;color:#38bdf8;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%]{display:flex;align-items:center;gap:6px;flex-shrink:0}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pre-post-toggle[_ngcontent-%COMP%]{display:inline-flex;align-items:center;background:#ffffff1f;border:1px solid rgba(255,255,255,.15);border-radius:6px;padding:2px;gap:2px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pre-post-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%]{border:none;background:transparent;color:#ffffffb3;font-size:10.5px;font-weight:700;padding:2px 7px;border-radius:4px;cursor:pointer;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pre-post-toggle[_ngcontent-%COMP%] .toggle-btn[_ngcontent-%COMP%]:hover{color:#fff;background:#fff3}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pre-post-toggle[_ngcontent-%COMP%] .toggle-btn.active[_ngcontent-%COMP%]{background:#0284c7;color:#fff;box-shadow:0 1px 3px #0000004d}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pin-card-btn[_ngcontent-%COMP%]{background:transparent;border:none;color:#fff9;cursor:pointer;padding:3px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pin-card-btn[_ngcontent-%COMP%]:hover{color:#fff;background:#ffffff26}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pin-card-btn.pinned[_ngcontent-%COMP%]{color:#38bdf8;background:#38bdf833}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .step-replay-container[_ngcontent-%COMP%] .step-image-viewport[_ngcontent-%COMP%] .step-frame-badge[_ngcontent-%COMP%] .badge-actions-right[_ngcontent-%COMP%] .pin-card-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:14px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-container[_ngcontent-%COMP%]{position:relative;width:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-container[_ngcontent-%COMP%] video[_ngcontent-%COMP%]{width:100%;height:auto;max-height:520px;display:block;object-fit:contain;cursor:pointer;background:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%]{background:#ffffffd6;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-top:1px solid rgba(226,232,240,.75);padding:12px 14px;display:flex;flex-direction:column;gap:10px;flex-shrink:0;box-shadow:0 -4px 16px #0f172a0a,inset 0 1px 1px #ffffffe6}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%]{position:relative;width:100%;height:8px;display:flex;align-items:center;cursor:pointer}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .scrubber-range[_ngcontent-%COMP%]{position:absolute;width:100%;height:100%;opacity:0;cursor:pointer;z-index:5;margin:0}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%]:before{content:"";position:absolute;left:0;right:0;height:5px;background:#e2e8f0d9;border-radius:3px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .scrubber-progress-fill[_ngcontent-%COMP%]{position:absolute;left:0;height:5px;background:linear-gradient(90deg,#0284c7,#2563eb);border-radius:3px;pointer-events:none;box-shadow:0 0 8px #0284c766}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .step-ticks-wrapper[_ngcontent-%COMP%]{position:absolute;left:0;right:0;height:100%;pointer-events:none;display:flex;align-items:center;z-index:6}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .step-ticks-wrapper[_ngcontent-%COMP%] .step-tick[_ngcontent-%COMP%]{position:absolute;width:7px;height:7px;border-radius:50%;background:#ffffffe6;border:1.5px solid #94a3b8;transform:translate(-50%);pointer-events:auto;cursor:pointer;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .step-ticks-wrapper[_ngcontent-%COMP%] .step-tick[_ngcontent-%COMP%]:hover{transform:translate(-50%) scale(1.4);border-color:#0284c7;background:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .step-ticks-wrapper[_ngcontent-%COMP%] .step-tick.passed[_ngcontent-%COMP%]{background:#0284c7;border-color:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .scrubber-bar-container[_ngcontent-%COMP%] .step-ticks-wrapper[_ngcontent-%COMP%] .step-tick.active[_ngcontent-%COMP%]{background:#38bdf8;border-color:#fff;transform:translate(-50%) scale(1.3);box-shadow:0 0 6px #38bdf8cc}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .controls-left[_ngcontent-%COMP%], .floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .controls-right[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn[_ngcontent-%COMP%]{background:#f1f5f9bf;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);border:1px solid rgba(226,232,240,.85);box-shadow:0 1px 3px #0f172a0a,inset 0 1px 1px #ffffffd9;color:#0f172a;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .2s cubic-bezier(.4,0,.2,1)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn[_ngcontent-%COMP%]:hover{background:#fffffff2;border-color:#bfdbfee6;color:#0284c7;transform:translateY(-.5px);box-shadow:0 3px 8px #0f172a14}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn[_ngcontent-%COMP%]:active{transform:scale(.96)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.active[_ngcontent-%COMP%]{color:#0284c7;background:#e0f2fed9;border-color:#38bdf8cc;box-shadow:0 2px 6px #0284c726}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.play-btn[_ngcontent-%COMP%]{background:linear-gradient(135deg,#0284c7eb,#2563ebe0);backdrop-filter:blur(14px) saturate(180%);-webkit-backdrop-filter:blur(14px) saturate(180%);border:1px solid rgba(255,255,255,.35);color:#fff;width:36px;height:36px;border-radius:50%;box-shadow:0 4px 14px #0284c759,inset 0 1px 1px #ffffff80}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.play-btn[_ngcontent-%COMP%]:hover{background:linear-gradient(135deg,#0284c7,#2563ebf2);box-shadow:0 6px 18px #0284c773,inset 0 1px 1px #fff9;transform:translateY(-1px) scale(1.04)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.play-btn[_ngcontent-%COMP%]:active{transform:scale(.96)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.play-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:22px;color:#fff}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.speed-pill-btn[_ngcontent-%COMP%]{width:auto;min-width:34px;padding:0 7px;font-size:11px;font-weight:700;font-family:JetBrains Mono,Roboto Mono,monospace;color:#334155;letter-spacing:-.2px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn.speed-pill-btn[_ngcontent-%COMP%]:hover{color:#0284c7;border-color:#bfdbfee6}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .ctrl-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px;color:#1e293b}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .video-controls-footer[_ngcontent-%COMP%] .controls-row[_ngcontent-%COMP%] .time-display[_ngcontent-%COMP%]{font-size:12px;font-weight:700;font-family:JetBrains Mono,Roboto Mono,monospace;color:#1e293b;margin-left:4px;letter-spacing:.5px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%]{background:#fff;padding:36px 20px;display:flex;flex-direction:column;align-items:center;text-align:center;box-sizing:border-box;width:100%}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .fallback-icon-wrap[_ngcontent-%COMP%]{width:48px;height:48px;border-radius:50%;background:#fef2f2;border:1px solid #fee2e2;display:flex;align-items:center;justify-content:center;margin-bottom:12px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .fallback-icon-wrap[_ngcontent-%COMP%] .fallback-icon[_ngcontent-%COMP%]{font-size:24px;color:#ef4444}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .fallback-title[_ngcontent-%COMP%]{margin:0 0 6px;font-size:13.5px;font-weight:600;color:#0f172a}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .fallback-desc[_ngcontent-%COMP%]{margin:0 0 12px;font-size:12px;color:#64748b;line-height:1.4;max-width:240px}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .retry-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:6px;background:#0284c7;color:#fff;border:none;border-radius:6px;padding:5px 12px;font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .retry-btn[_ngcontent-%COMP%]:hover{background:#0369a1;transform:translateY(-1px)}.floating-video-wrapper[_ngcontent-%COMP%] .video-body[_ngcontent-%COMP%] .empty-video-fallback[_ngcontent-%COMP%] .retry-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}@keyframes _ngcontent-%COMP%_popIn{0%{opacity:0;transform:scale(.96) translateY(6px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes _ngcontent-%COMP%_fadeIn{0%{opacity:0}to{opacity:1}}@keyframes _ngcontent-%COMP%_spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes _ngcontent-%COMP%_pulseRed{0%,to{opacity:1}50%{opacity:.5}} .action-point-marker{z-index:10;pointer-events:none}@keyframes _ngcontent-%COMP%_pulse-animation{0%{transform:scale(.6);opacity:1}to{transform:scale(2.2);opacity:0}}']})};var L4=["dockInput"];function V4(n,t){if(n&1){let e=L();l(0,"div",4)(1,"span",55),u(2,"error"),d(),l(3,"span",56),u(4),d(),l(5,"button",57),k("click",function(){v(e);let i=m();return y(i.errorMessage.set(null))}),l(6,"span",42),u(7,"close"),d()()()}if(n&2){let e=m();g(4),O(e.errorMessage())}}function j4(n,t){if(n&1){let e=L();l(0,"button",58),k("click",function(i){v(e);let r=m();return y(r.clearInput(i))}),l(1,"span",42),u(2,"close"),d()()}}function $4(n,t){if(n&1){let e=L();l(0,"button",59),k("click",function(i){v(e);let r=m();return y(r.stopTask(i))}),l(1,"span",42),u(2,"stop"),d()()}if(n&2){let e=m();R("disabled",e.isSubmitting())("title",e.stopButtonTitle()),Wt("aria-label",e.stopButtonTitle())}}function z4(n,t){n&1&&(l(0,"span",41),u(1,"sync"),d())}function B4(n,t){n&1&&(l(0,"span",42),u(1,"arrow_upward"),d())}function H4(n,t){if(n&1){let e=L();l(0,"button",60),k("click",function(i){v(e);let r=m();return y(r.stopTask(i))}),l(1,"span",42),u(2,"stop"),d()()}if(n&2){let e=m();R("disabled",e.isSubmitting())("title",e.stopButtonTitle()),Wt("aria-label",e.stopButtonTitle())}}var Yd=class n{agentService=D(It);zone=D(ge);destroyRef=D(Ae);rightPanelWidth=x(typeof window<"u"?Math.round(window.innerWidth/3):450);isDragging=x(!1);dragWidthRafId=null;pendingDragWidth=0;taskInputSignal=x("");get taskInput(){return this.taskInputSignal()}set taskInput(t){this.taskInputSignal.set(t)}isSubmitting=x(!1);errorMessage=x(null);selectedProfile=x("flash");isHoveringCard=x(!1);isInputFocused=x(!1);dockInputRef;ngOnInit(){if(typeof localStorage<"u"){let t=localStorage.getItem("artemis_selected_profile");(t==="flash"||t==="pro")&&this.selectedProfile.set(t)}this.zone.runOutsideAngular(()=>{window.addEventListener("keydown",this.onGlobalKeyDown)}),this.destroyRef.onDestroy(()=>{window.removeEventListener("keydown",this.onGlobalKeyDown),this.detachDragListeners()})}setProfile(t,e){e&&e.stopPropagation(),this.selectedProfile.set(t),typeof localStorage<"u"&&localStorage.setItem("artemis_selected_profile",t)}isTaskRunning=I(()=>this.agentService.isCurrentSessionRunning());stopButtonTitle=I(()=>{let t=this.agentService.currentSession();if(t?.initial_goal)return`Stop current task: "${t.initial_goal.length>45?t.initial_goal.substring(0,42)+"...":t.initial_goal}"`;let e=this.agentService.currentSessionId();return e?`Stop current task (${e})`:"Stop current running task"});isBarExpanded=I(()=>this.isHoveringCard()||this.isInputFocused()||this.taskInput.trim().length>0);onInputFocus(){this.isInputFocused.set(!0)}onInputBlur(){this.isInputFocused.set(!1)}onCardClick(t){let e=t.target;e.closest("button")||e.tagName.toLowerCase()==="textarea"||this.focusInput()}focusInput(){setTimeout(()=>{this.dockInputRef?.nativeElement?.focus()},30)}clearInput(t){t&&t.stopPropagation(),this.taskInput="",this.dockInputRef?.nativeElement&&(this.dockInputRef.nativeElement.style.height="auto",this.dockInputRef.nativeElement.focus())}onTextareaInput(t){let e=t.target;if(!e)return;e.style.height="auto";let o=Math.min(Math.max(e.scrollHeight,24),120);e.style.height=`${o}px`}onGlobalKeyDown=t=>{(t.metaKey||t.ctrlKey)&&t.key.toLowerCase()==="k"&&(t.preventDefault(),this.focusInput())};onKeyDown(t){t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),this.submitTask())}submitTask(){let t=this.taskInput.trim();!t||this.isSubmitting()||(this.isSubmitting.set(!0),this.errorMessage.set(null),this.dockInputRef?.nativeElement&&this.dockInputRef.nativeElement.blur(),this.isInputFocused.set(!1),this.agentService.runTask(t,this.selectedProfile()).subscribe({next:e=>{this.taskInput="",this.dockInputRef?.nativeElement&&(this.dockInputRef.nativeElement.style.height="auto"),this.isSubmitting.set(!1),this.agentService.fetchStatus()},error:e=>{console.error("Failed to submit task:",e),this.isSubmitting.set(!1),this.errorMessage.set(e.error?.detail||"The runner is busy. Please wait for current task to finish."),setTimeout(()=>{this.errorMessage.set(null)},5e3)}}))}stopTask(t){if(t&&t.stopPropagation(),!this.isTaskRunning())return;let e=this.agentService.currentSessionId();this.isSubmitting.set(!0),this.errorMessage.set(null),this.agentService.stopTask(e,!1),setTimeout(()=>{this.isSubmitting.set(!1)},400)}onDragStart(t){this.isDragging.set(!0),t.preventDefault(),this.zone.runOutsideAngular(()=>{document.addEventListener("mousemove",this.onMouseMove),document.addEventListener("mouseup",this.onMouseUp)})}onMouseMove=t=>{if(!this.isDragging())return;let e=window.innerWidth-t.clientX,o=250,i=window.innerWidth-300;e>=o&&e<=i&&(this.pendingDragWidth=e,this.dragWidthRafId===null&&(this.dragWidthRafId=requestAnimationFrame(()=>{this.dragWidthRafId=null,this.rightPanelWidth.set(this.pendingDragWidth)})))};onMouseUp=()=>{this.isDragging.set(!1),this.detachDragListeners()};detachDragListeners(){document.removeEventListener("mousemove",this.onMouseMove),document.removeEventListener("mouseup",this.onMouseUp),this.dragWidthRafId!==null&&(cancelAnimationFrame(this.dragWidthRafId),this.dragWidthRafId=null)}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-workspace"]],viewQuery:function(e,o){if(e&1&&fr(L4,5),e&2){let i;eo(i=to())&&(o.dockInputRef=i.first)}},decls:67,vars:33,consts:[["dockInput",""],[1,"workspace-container"],[1,"left-panel","glass-panel"],[1,"workspace-floating-bar-wrapper"],[1,"floating-dock-toast","error"],[1,"floating-dock-row"],[1,"floating-dock-card",3,"click","mouseenter","mouseleave"],[1,"dock-wave-container"],[1,"wave-glow-ambient"],[1,"wave-tracks-wrapper"],["viewBox","0 0 2400 80","preserveAspectRatio","none",1,"liquid-wave","wave-back"],["id","waveGradBack","x1","0%","y1","0%","x2","100%","y2","0%"],["offset","0%",1,"wave-stop-1"],["offset","25%",1,"wave-stop-2"],["offset","50%",1,"wave-stop-3"],["offset","75%",1,"wave-stop-4"],["offset","100%",1,"wave-stop-1"],["d","M 0,25 Q 300,55 600,20 T 1200,25 Q 1500,55 1800,20 T 2400,25 L 2400,80 L 0,80 Z","fill","url(#waveGradBack)"],["viewBox","0 0 2400 80","preserveAspectRatio","none",1,"liquid-wave","wave-mid"],["id","waveGradMid","x1","0%","y1","0%","x2","100%","y2","0%"],["offset","0%",1,"wave-stop-3"],["offset","35%",1,"wave-stop-1"],["offset","70%",1,"wave-stop-4"],["offset","100%",1,"wave-stop-3"],["d","M 0,35 Q 300,5 600,45 T 1200,35 Q 1500,5 1800,45 T 2400,35 L 2400,80 L 0,80 Z","fill","url(#waveGradMid)"],["viewBox","0 0 2400 80","preserveAspectRatio","none",1,"liquid-wave","wave-front"],["id","waveGradFront","x1","0%","y1","0%","x2","100%","y2","0%"],["offset","0%",1,"wave-stop-2"],["offset","30%",1,"wave-stop-4"],["offset","65%",1,"wave-stop-1"],["offset","100%",1,"wave-stop-2"],["d","M 0,18 Q 300,50 600,15 T 1200,18 Q 1500,50 1800,15 T 2400,18 L 2400,80 L 0,80 Z","fill","url(#waveGradFront)"],[1,"capsule-peek-view"],[1,"capsule-placeholder"],[1,"expanded-card-content"],[1,"card-main-input-row"],[1,"input-content-area"],["rows","1","spellcheck","false",1,"dock-textarea",3,"ngModelChange","input","keydown","focus","blur","ngModel","disabled","placeholder"],["type","button","title","Clear input text","aria-label","Clear input text",1,"dock-circle-btn","btn-clear"],["type","button",1,"dock-circle-btn","btn-stop",3,"disabled","title"],["type","button","aria-label","Start task",1,"dock-circle-btn","btn-send",3,"click","disabled","title"],[1,"material-symbols-outlined","spinning-icon"],[1,"material-symbols-outlined"],[1,"card-footer-row"],[1,"footer-left-group"],["role","group","aria-label","Select Agent Architecture",1,"model-profile-toggle"],["type","button","title","Flash: fast single-look loop for everyday app tasks (3\u20135s/step)",1,"profile-toggle-pill",3,"mousedown","click"],[1,"material-symbols-outlined","pill-icon","icon-flash"],[1,"pill-label"],["type","button","title","Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks (15\u201340s/step)",1,"profile-toggle-pill",3,"mousedown","click"],[1,"material-symbols-outlined","pill-icon","icon-pro"],["type","button",1,"standalone-stop-btn",3,"disabled","title"],[1,"resizer",3,"mousedown"],[1,"resizer-handle"],[1,"right-panel"],[1,"material-symbols-outlined","toast-icon"],[1,"toast-text"],["type","button","aria-label","Close error message",1,"btn-toast-close",3,"click"],["type","button","title","Clear input text","aria-label","Clear input text",1,"dock-circle-btn","btn-clear",3,"click"],["type","button",1,"dock-circle-btn","btn-stop",3,"click","disabled","title"],["type","button",1,"standalone-stop-btn",3,"click","disabled","title"]],template:function(e,o){if(e&1){let i=L();l(0,"div",1)(1,"main",2),$(2,"app-agent-stream"),l(3,"div",3),M(4,V4,8,1,"div",4),l(5,"div",5)(6,"div",6),k("click",function(a){return o.onCardClick(a)})("mouseenter",function(){return o.isHoveringCard.set(!0)})("mouseleave",function(){return o.isHoveringCard.set(!1)}),l(7,"div",7),$(8,"div",8),l(9,"div",9),yo(),l(10,"svg",10)(11,"defs")(12,"linearGradient",11),$(13,"stop",12)(14,"stop",13)(15,"stop",14)(16,"stop",15)(17,"stop",16),d()(),$(18,"path",17),d(),l(19,"svg",18)(20,"defs")(21,"linearGradient",19),$(22,"stop",20)(23,"stop",21)(24,"stop",22)(25,"stop",23),d()(),$(26,"path",24),d(),l(27,"svg",25)(28,"defs")(29,"linearGradient",26),$(30,"stop",27)(31,"stop",28)(32,"stop",29)(33,"stop",30),d()(),$(34,"path",31),d()()(),rr(),l(35,"div",32)(36,"span",33),u(37,"Start a new task..."),d()(),l(38,"div",34)(39,"div",35)(40,"div",36)(41,"textarea",37,0),hr("ngModelChange",function(a){return v(i),$a(o.taskInput,a)||(o.taskInput=a),y(a)}),k("input",function(a){return o.onTextareaInput(a)})("keydown",function(a){return o.onKeyDown(a)})("focus",function(){return o.onInputFocus()})("blur",function(){return o.onInputBlur()}),d(),Rt(),d(),M(43,j4,3,0,"button",38),M(44,$4,3,3,"button",39),l(45,"button",40),k("click",function(){return o.submitTask()}),M(46,z4,2,0,"span",41),M(47,B4,2,0,"span",42),d()(),l(48,"div",43)(49,"div",44)(50,"div",45)(51,"button",46),k("mousedown",function(a){return a.preventDefault()})("click",function(a){return o.setProfile("flash",a)}),l(52,"span",47),u(53,"bolt"),d(),l(54,"span",48),u(55,"Flash"),d()(),l(56,"button",49),k("mousedown",function(a){return a.preventDefault()})("click",function(a){return o.setProfile("pro",a)}),l(57,"span",50),u(58,"psychology"),d(),l(59,"span",48),u(60,"Pro"),d()()()()()()(),M(61,H4,3,3,"button",51),d()()(),l(62,"div",52),k("mousedown",function(a){return o.onDragStart(a)}),$(63,"div",53),d(),l(64,"aside",54),$(65,"app-chat-interface"),d(),$(66,"app-floating-video-player"),d()}e&2&&(w("is-resizing",o.isDragging()),g(3),w("is-expanded",o.isBarExpanded())("is-dormant",!o.isBarExpanded())("is-focused",o.isInputFocused())("has-text",!!o.taskInput.trim())("is-running",o.isTaskRunning()),g(),b(o.errorMessage()?4:-1),g(3),w("is-pro",o.selectedProfile()==="pro"),g(34),_r("ngModel",o.taskInput),R("disabled",o.isSubmitting())("placeholder",o.selectedProfile()==="pro"?"Pro: plans first, checks results and recovers from blocked steps. For long or complex tasks":"Flash: fast single-look loop for everyday app tasks"),Ft(),g(2),b(o.taskInput.trim()&&!o.isSubmitting()?43:-1),g(),b(o.isTaskRunning()?44:-1),g(),w("has-content",!!o.taskInput.trim()),R("disabled",!o.taskInput.trim()||o.isSubmitting())("title",o.isSubmitting()?"Starting task...":"Start task (Enter)"),g(),b(o.isSubmitting()?46:-1),g(),b(o.isSubmitting()?-1:47),g(4),w("active",o.selectedProfile()==="flash"),g(5),w("active",o.selectedProfile()==="pro"),g(5),b(o.isTaskRunning()?61:-1),g(3),Se("width",o.rightPanelWidth(),"px"))},dependencies:[Qt,zr,Nd,ws,Wd,qr,Kd],styles:["[_nghost-%COMP%]{display:block;height:100vh;width:100vw;overflow:hidden;font-family:Google Sans Text,Google Sans,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}.workspace-container[_ngcontent-%COMP%]{position:relative;display:flex;height:100vh;width:100vw;background-color:#e2e5ea;overflow:hidden}.workspace-container.is-resizing[_ngcontent-%COMP%]{cursor:col-resize;-webkit-user-select:none;user-select:none}.workspace-container[_ngcontent-%COMP%] .glass-panel[_ngcontent-%COMP%]{position:relative;z-index:1;background:#f4fafb;border:none;box-shadow:none}.workspace-container[_ngcontent-%COMP%] .left-panel[_ngcontent-%COMP%]{flex:1 0 500px;display:flex;flex-direction:column;overflow:hidden;background:#f4fafb;border-right:1px solid #e2e8f0;border-left:none;border-top:none;border-bottom:none;min-width:450px;height:100%}.workspace-container[_ngcontent-%COMP%] .right-panel[_ngcontent-%COMP%]{flex:0 1 auto;min-width:250px;max-width:100%;padding:0;border:none;background-color:#fff;box-shadow:-6px 0 24px #00000008;height:100%;display:flex;flex-direction:column;box-sizing:border-box;z-index:2}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]{width:12px;cursor:col-resize;display:flex;align-items:center;justify-content:center;z-index:10;transition:background .2s ease;margin:0 -6px}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]:hover, .workspace-container.is-resizing[_ngcontent-%COMP%] .workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]{background:#ffffff26}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]:hover .resizer-handle[_ngcontent-%COMP%], .workspace-container.is-resizing[_ngcontent-%COMP%] .workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%] .resizer-handle[_ngcontent-%COMP%]{background:#5e72e4cc;height:60px}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%] .resizer-handle[_ngcontent-%COMP%]{width:4px;height:40px;background:#00000026;border-radius:4px;transition:background .2s ease,height .2s ease}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%]{position:absolute;bottom:18px;left:50%;transform:translate(-50%);width:100%;max-width:min(680px,100% - 36px);z-index:50;display:flex;flex-direction:column;align-items:center;pointer-events:none;transition:all .25s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast[_ngcontent-%COMP%]{pointer-events:auto;margin-bottom:8px;display:flex;align-items:center;gap:8px;padding:7px 14px;border-radius:12px;font-size:12.5px;font-weight:500;box-shadow:0 8px 24px -4px #ef444426;animation:_ngcontent-%COMP%_slideUpFade .2s ease-out;max-width:90%}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%]{background:#fef2f2e0;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border:1px solid rgba(254,202,202,.9);color:#991b1b}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%] .toast-icon[_ngcontent-%COMP%]{font-size:16px;color:#ef4444;flex-shrink:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%] .toast-text[_ngcontent-%COMP%]{flex:1;line-height:1.4}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%] .btn-toast-close[_ngcontent-%COMP%]{background:transparent;border:none;padding:2px;cursor:pointer;color:#991b1b;display:flex;align-items:center;justify-content:center;border-radius:4px;opacity:.7;transition:opacity .15s ease}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%] .btn-toast-close[_ngcontent-%COMP%]:hover{opacity:1;background:#0000000d}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-toast.error[_ngcontent-%COMP%] .btn-toast-close[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:15px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;pointer-events:none;transition:gap .32s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%]{pointer-events:auto;display:flex;align-items:center;justify-content:center;width:34px;height:34px;min-width:34px;border-radius:50%;border:1px solid rgba(255,255,255,.85);background:#ef4444e6;backdrop-filter:blur(24px) saturate(190%) brightness(105%);-webkit-backdrop-filter:blur(24px) saturate(190%) brightness(105%);color:#fff;box-shadow:0 6px 18px -2px #ef444466,inset 0 1px 1.5px #ffffffb3;cursor:pointer;outline:none;flex-shrink:0;transition:opacity .28s cubic-bezier(.16,1,.3,1),transform .28s cubic-bezier(.16,1,.3,1),width .28s cubic-bezier(.16,1,.3,1),min-width .28s cubic-bezier(.16,1,.3,1),margin .28s cubic-bezier(.16,1,.3,1),background .2s ease,box-shadow .2s ease;animation:_ngcontent-%COMP%_popIn .25s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:17px;line-height:1}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%]:hover:not(:disabled){background:#dc2626;transform:scale(1.1);box-shadow:0 8px 22px #ef44448c,inset 0 1px 1.5px #ffffffe6}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.93)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%]:disabled{opacity:.6;cursor:not-allowed}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]{pointer-events:auto;position:relative;width:100%;border-radius:18px;box-sizing:border-box;overflow:hidden;transition:max-width .32s cubic-bezier(.16,1,.3,1),width .32s cubic-bezier(.16,1,.3,1),border-radius .28s cubic-bezier(.16,1,.3,1),box-shadow .28s ease,background .28s ease,transform .28s cubic-bezier(.16,1,.3,1),border-color .28s ease}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%]{position:absolute;bottom:0;left:0;right:0;height:100%;pointer-events:none;border-radius:inherit;overflow:hidden;z-index:0;opacity:.78;transition:opacity .35s ease,filter .35s ease}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-stop-1[_ngcontent-%COMP%]{stop-color:#3b82f6;stop-opacity:.65}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-stop-2[_ngcontent-%COMP%]{stop-color:#8b5cf6;stop-opacity:.7}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-stop-3[_ngcontent-%COMP%]{stop-color:#f59e0b;stop-opacity:.65}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-stop-4[_ngcontent-%COMP%]{stop-color:#06b6d4;stop-opacity:.6}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container.is-pro[_ngcontent-%COMP%] .wave-stop-1[_ngcontent-%COMP%]{stop-color:#7c3aed;stop-opacity:.7}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container.is-pro[_ngcontent-%COMP%] .wave-stop-2[_ngcontent-%COMP%]{stop-color:#ec4899;stop-opacity:.75}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container.is-pro[_ngcontent-%COMP%] .wave-stop-3[_ngcontent-%COMP%]{stop-color:#6366f1;stop-opacity:.65}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container.is-pro[_ngcontent-%COMP%] .wave-stop-4[_ngcontent-%COMP%]{stop-color:#a855f7;stop-opacity:.7}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-glow-ambient[_ngcontent-%COMP%]{position:absolute;bottom:-8px;left:5%;right:5%;height:30px;border-radius:50%;background:radial-gradient(ellipse at center,rgba(99,102,241,.4) 0%,rgba(245,158,11,.2) 50%,transparent 80%);filter:blur(16px);opacity:.7;animation:_ngcontent-%COMP%_glowBreathe 5s ease-in-out infinite alternate}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container.is-pro[_ngcontent-%COMP%] .wave-glow-ambient[_ngcontent-%COMP%]{background:radial-gradient(ellipse at center,rgba(168,85,247,.45) 0%,rgba(236,72,153,.25) 50%,transparent 80%)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%]{position:absolute;bottom:0;left:0;width:100%;height:22px;overflow:hidden;mask-image:linear-gradient(to top,rgba(0,0,0,.9) 0%,rgba(0,0,0,.5) 60%,transparent 100%);-webkit-mask-image:linear-gradient(to top,rgba(0,0,0,.9) 0%,rgba(0,0,0,.5) 60%,transparent 100%)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%] .liquid-wave[_ngcontent-%COMP%]{position:absolute;bottom:0;left:0;width:200%;height:100%;will-change:transform;filter:blur(8px)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%] .liquid-wave.wave-back[_ngcontent-%COMP%]{opacity:.45;animation:_ngcontent-%COMP%_waveSlide 12s ease-in-out infinite alternate}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%] .liquid-wave.wave-mid[_ngcontent-%COMP%]{opacity:.6;animation:_ngcontent-%COMP%_waveSlideReverse 9s ease-in-out infinite alternate}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%] .liquid-wave.wave-front[_ngcontent-%COMP%]{opacity:.75;animation:_ngcontent-%COMP%_waveSlide 6.5s ease-in-out infinite alternate}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]{max-width:160px;width:160px;margin:0;background:#ffffffa6;backdrop-filter:blur(26px) saturate(190%) brightness(104%);-webkit-backdrop-filter:blur(26px) saturate(190%) brightness(104%);border:1px solid rgba(255,255,255,.85);border-radius:17px;box-shadow:0 6px 18px -2px #0f172a12,inset 0 1px 1.5px #fffffff2,inset 0 -1px 1px #ffffff4d;cursor:pointer;opacity:.92;transform:translateY(2px)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%]{opacity:.78}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%]{height:12px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]:hover{opacity:1;transform:translateY(0);background:#ffffffd1;border-color:#fff;box-shadow:0 8px 24px -2px #0f172a1f,inset 0 1px 1.5px #fff}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]:hover .dock-wave-container[_ngcontent-%COMP%]{opacity:.95;filter:drop-shadow(0 0 6px rgba(99,102,241,.35))}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .capsule-peek-view[_ngcontent-%COMP%]{display:flex;opacity:1}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-dormant[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%]{display:none;opacity:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .capsule-peek-view[_ngcontent-%COMP%]{position:relative;z-index:1;display:flex;align-items:center;justify-content:center;padding:6px 14px;min-height:34px;box-sizing:border-box;-webkit-user-select:none;user-select:none}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .capsule-peek-view[_ngcontent-%COMP%] .capsule-placeholder[_ngcontent-%COMP%]{color:#475569;font-size:12px;font-weight:500;letter-spacing:.1px;white-space:nowrap;text-align:center}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-row[_ngcontent-%COMP%]{gap:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .standalone-stop-btn[_ngcontent-%COMP%]{opacity:0;width:0;min-width:0;margin:0;padding:0;border-width:0;transform:scale(.5);pointer-events:none}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]{max-width:100%;width:100%;background:#ffffffad;backdrop-filter:blur(32px) saturate(210%) brightness(105%);-webkit-backdrop-filter:blur(32px) saturate(210%) brightness(105%);border:1px solid rgba(255,255,255,.82);box-shadow:0 16px 44px -8px #0f172a1f,0 2px 8px #00000005,inset 0 1.5px 2px #fffffff2,inset 0 -1px 2px #ffffff4d;opacity:1;transform:translateY(0)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%]{opacity:.88}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%] .wave-tracks-wrapper[_ngcontent-%COMP%]{height:22px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .capsule-peek-view[_ngcontent-%COMP%]{display:none;opacity:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-expanded[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%]{display:block;opacity:1;animation:_ngcontent-%COMP%_cardFadeIn .25s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-focused[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%]{background:#ffffffc7;backdrop-filter:blur(36px) saturate(220%) brightness(106%);-webkit-backdrop-filter:blur(36px) saturate(220%) brightness(106%);border-color:#3b82f68c;box-shadow:0 20px 48px -6px #2563eb29,0 0 0 2.5px #3b82f62e,inset 0 1.5px 2px #fff}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper.is-focused[_ngcontent-%COMP%] .floating-dock-card[_ngcontent-%COMP%] .dock-wave-container[_ngcontent-%COMP%]{opacity:1;filter:drop-shadow(0 0 8px rgba(59,130,246,.35))}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%]{position:relative;z-index:1;display:flex;flex-direction:column;width:100%}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%]{display:flex;align-items:center;gap:10px;padding:8px 10px 3px 14px;min-height:48px;box-sizing:border-box}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .leading-status-icon[_ngcontent-%COMP%]{width:14px;height:14px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .leading-status-icon[_ngcontent-%COMP%] .pulse-indicator-dot[_ngcontent-%COMP%]{width:8px;height:8px;border-radius:50%;background:#ef4444;box-shadow:0 0 #ef444499;animation:_ngcontent-%COMP%_pulseDot 1.4s infinite}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .input-content-area[_ngcontent-%COMP%]{flex:1;min-width:0;display:flex;align-items:center}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .input-content-area[_ngcontent-%COMP%] .dock-textarea[_ngcontent-%COMP%]{width:100%;border:none;outline:none;background:transparent;resize:none;font-family:inherit;font-size:13.5px;font-weight:450;line-height:1.5;color:#0f172a;padding:3px 0;max-height:120px;box-sizing:border-box;display:block}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .input-content-area[_ngcontent-%COMP%] .dock-textarea[_ngcontent-%COMP%]::placeholder{color:#64748b;font-size:12.5px;font-weight:400;transition:color .15s ease}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .input-content-area[_ngcontent-%COMP%] .dock-textarea[_ngcontent-%COMP%]:disabled{opacity:.6;cursor:not-allowed}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn[_ngcontent-%COMP%]{border-radius:50%;display:flex;align-items:center;justify-content:center;border:none;cursor:pointer;outline:none;flex-shrink:0;transition:background .18s cubic-bezier(.16,1,.3,1),color .18s cubic-bezier(.16,1,.3,1),transform .18s cubic-bezier(.16,1,.3,1),box-shadow .18s cubic-bezier(.16,1,.3,1),border-color .18s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn[_ngcontent-%COMP%] .material-symbols-outlined[_ngcontent-%COMP%]{font-size:18px;line-height:1}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn[_ngcontent-%COMP%] .spinning-icon[_ngcontent-%COMP%]{font-size:17px;animation:_ngcontent-%COMP%_spin 1s linear infinite}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-clear[_ngcontent-%COMP%]{width:26px;height:26px;background:#ffffff73;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);border:1px solid rgba(255,255,255,.6);color:#64748b}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-clear[_ngcontent-%COMP%]:hover{background:#ffffffbf;color:#0f172a}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-stop[_ngcontent-%COMP%]{width:36px;height:36px;background:#ef4444e6;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#fff;border:1px solid rgba(255,255,255,.35);box-shadow:0 4px 12px #ef44444d;animation:_ngcontent-%COMP%_popIn .2s cubic-bezier(.16,1,.3,1)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-stop[_ngcontent-%COMP%]:hover:not(:disabled){background:#dc2626;transform:scale(1.05);box-shadow:0 6px 16px #ef444466}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-stop[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.95)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-stop[_ngcontent-%COMP%]:disabled{opacity:.6;cursor:not-allowed}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-send[_ngcontent-%COMP%]{width:36px;height:36px;background:#0f172ae0;-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.25);color:#fff;box-shadow:0 4px 14px #0f172a33,inset 0 1px 1px #ffffff40}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-send[_ngcontent-%COMP%]:disabled{background:#e2e8f099;color:#94a3b8;cursor:not-allowed;box-shadow:none;border-color:#ffffff80;transform:none}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-send.has-content[_ngcontent-%COMP%]:not(:disabled){background:linear-gradient(135deg,#1e293bf2,#0f172afa);box-shadow:0 4px 14px #0f172a40,inset 0 1px 1px #ffffff4d}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-send.has-content[_ngcontent-%COMP%]:not(:disabled):hover{background:linear-gradient(135deg,#2563eb,#1d4ed8);transform:scale(1.05);box-shadow:0 6px 18px #2563eb59}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-main-input-row[_ngcontent-%COMP%] .dock-circle-btn.btn-send[_ngcontent-%COMP%]:active:not(:disabled){transform:scale(.95)}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%]{display:flex;align-items:center;justify-content:space-between;padding:3px 14px 8px;box-sizing:border-box}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%]{display:inline-flex;align-items:center;background:#ffffff73;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid rgba(255,255,255,.65);box-shadow:inset 0 1px 1px #fffc,0 1px 3px #00000008;border-radius:8px;padding:2px;gap:2px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:3.5px;width:62px;min-width:62px;height:22px;border-radius:6px;border:none;background:transparent;color:#64748b;font-family:inherit;font-size:11px;font-weight:550;cursor:pointer;transition:color .16s cubic-bezier(.16,1,.3,1),background .16s cubic-bezier(.16,1,.3,1),box-shadow .16s cubic-bezier(.16,1,.3,1);outline:none;-webkit-user-select:none;user-select:none;box-sizing:border-box}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill[_ngcontent-%COMP%] .pill-icon[_ngcontent-%COMP%]{font-size:13px;line-height:1;flex-shrink:0}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill[_ngcontent-%COMP%] .pill-label[_ngcontent-%COMP%]{line-height:1}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill[_ngcontent-%COMP%]:hover:not(.active){color:#1e293b;background:#ffffffa6}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill.active[_ngcontent-%COMP%]{background:#ffffffeb;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);color:#0f172a;font-weight:700;box-shadow:0 1.5px 4px #00000014,inset 0 1px 1.5px #fff}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill.active[_ngcontent-%COMP%] .icon-flash[_ngcontent-%COMP%]{color:#d97706}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-left-group[_ngcontent-%COMP%] .model-profile-toggle[_ngcontent-%COMP%] .profile-toggle-pill.active[_ngcontent-%COMP%] .icon-pro[_ngcontent-%COMP%]{color:#7c3aed}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-right-status[_ngcontent-%COMP%] .running-status-pill[_ngcontent-%COMP%]{display:inline-flex;align-items:center;gap:4.5px;font-size:10.5px;font-weight:600;color:#dc2626;background:#fee2e2b3;padding:2px 7px;border-radius:10px}.workspace-container[_ngcontent-%COMP%] .workspace-floating-bar-wrapper[_ngcontent-%COMP%] .expanded-card-content[_ngcontent-%COMP%] .card-footer-row[_ngcontent-%COMP%] .footer-right-status[_ngcontent-%COMP%] .running-status-pill[_ngcontent-%COMP%] .running-pulse-dot[_ngcontent-%COMP%]{width:5.5px;height:5.5px;border-radius:50%;background:#ef4444;animation:_ngcontent-%COMP%_pulseDot 1.2s infinite}@media(max-width:1150px){.workspace-container[_ngcontent-%COMP%] .right-panel[_ngcontent-%COMP%], .workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]{display:none!important}.workspace-container[_ngcontent-%COMP%] .left-panel[_ngcontent-%COMP%]{border-right:none!important;width:100%!important;flex:1 1 100%!important;min-width:0!important}}@keyframes _ngcontent-%COMP%_popIn{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}@keyframes _ngcontent-%COMP%_spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes _ngcontent-%COMP%_slideUpFade{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_cardFadeIn{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes _ngcontent-%COMP%_fadeIn{0%{opacity:0}to{opacity:1}}@keyframes _ngcontent-%COMP%_pulseDot{0%{transform:scale(.95);box-shadow:0 0 #ef4444b3}70%{transform:scale(1);box-shadow:0 0 0 6px #ef444400}to{transform:scale(.95);box-shadow:0 0 #ef444400}}@keyframes _ngcontent-%COMP%_waveSlide{0%{transform:translateZ(0)}to{transform:translate3d(-50%,0,0)}}@keyframes _ngcontent-%COMP%_waveSlideReverse{0%{transform:translate3d(-50%,0,0)}to{transform:translateZ(0)}}@keyframes _ngcontent-%COMP%_glowBreathe{0%{opacity:.55;transform:scaleX(.92)}to{opacity:.95;transform:scaleX(1.08)}}@media(prefers-reduced-motion:reduce){.liquid-wave[_ngcontent-%COMP%], .wave-glow-ambient[_ngcontent-%COMP%], .pulse-indicator-dot[_ngcontent-%COMP%], .running-pulse-dot[_ngcontent-%COMP%]{animation:none!important}}"]})};var U4=(n,t)=>[n,t];function G4(n,t){if(n&1&&(l(0,"option",8),u(1),it(2,"date"),d()),n&2){let e=t.$implicit;R("value",e.session_id),g(),mr(" ",e.initial_goal||"Unnamed Goal"," (",e.device_serial?e.device_serial+" \xB7 ":"","",Et(2,4,e.start_time*1e3,"short"),") ")}}function q4(n,t){n&1&&(l(0,"div",11)(1,"div",13),u(2,"\u{1F4E1}"),d(),l(3,"h3"),u(4,"No Stream Activity Selected"),d(),l(5,"p"),u(6,"Please select a running session from the dropdown above to view real-time log payloads."),d()())}function W4(n,t){if(n&1&&(l(0,"div",22)(1,"div",27),u(2,"\u{1F4AC} Message to User / Summary:"),d(),l(3,"div",28),u(4),d()()),n&2){let e=m(2).$implicit;g(4),O(e.data.summary)}}function K4(n,t){if(n&1&&(l(0,"details",23)(1,"summary",29),u(2,"\u{1F9E0} Model Thinking Process (Click to expand)"),d(),l(3,"div",30),u(4),d()()),n&2){let e=m(2).$implicit;g(4),O(e.data.operator_native_thinking)}}function Y4(n,t){if(n&1&&(l(0,"details",24)(1,"summary",29),u(2,"\u{1F4DD} Raw Plan / Thoughts (Click to expand)"),d(),l(3,"div",31),u(4),d()()),n&2){let e=m(2).$implicit;g(4),O(e.data.operator_raw_thinking)}}function Z4(n,t){if(n&1&&(l(0,"details",25)(1,"summary",29),u(2,"\u{1F6E0}\uFE0F Tool Call Details (Click to expand)"),d(),l(3,"div",32)(4,"pre",33)(5,"code"),u(6),d()()()()),n&2){let e=m(2).$implicit,o=m(2);R("open",o.isShortContent(e.data.action_taken)),g(6),O(o.formatJson(e.data.action_taken))}}function Q4(n,t){if(n&1&&(l(0,"details",26)(1,"summary",29),u(2,"\u2713 Execution Result (Click to expand)"),d(),l(3,"div",32)(4,"pre",33)(5,"code"),u(6),d()()()()),n&2){let e=m(2).$implicit,o=m(2);g(6),O(o.formatJson(e.data.last_execution_result))}}function J4(n,t){if(n&1&&(l(0,"div",20),M(1,W4,5,1,"div",22),M(2,K4,5,1,"details",23),M(3,Y4,5,1,"details",24),M(4,Z4,7,2,"details",25),M(5,Q4,7,1,"details",26),d()),n&2){let e=m().$implicit;g(),b(e.data.summary?1:-1),g(),b(e.data.operator_native_thinking?2:-1),g(),b(e.data.operator_raw_thinking?3:-1),g(),b(e.data.action_taken?4:-1),g(),b(e.data.last_execution_result?5:-1)}}function X4(n,t){if(n&1&&(l(0,"div",21)(1,"details",34)(2,"summary",29),u(3,"\u{1F4C4} Raw Payload Data (Click to expand)"),d(),l(4,"div",32)(5,"pre",33)(6,"code"),u(7),d()()()()()),n&2){let e=m().$implicit,o=m(2);g(),R("open",o.isShortContent(e.data)),g(6),O(o.formatJson(e.data))}}function e5(n,t){if(n&1&&(l(0,"div",14)(1,"div",15)(2,"div",16)(3,"span",17),u(4),d(),l(5,"span",18),u(6),d()(),l(7,"span",19),u(8),it(9,"date"),d()(),M(10,J4,6,5,"div",20),M(11,X4,8,2,"div",21),d()),n&2){let e=t.$implicit,o=t.$index,i=m(2);R("ngClass",Gp(10,U4,"type-"+e.type,i.isStepEvent(e.type)?"step-event-card":"raw-event-card")),g(4),V("#",o+1),g(),R("ngClass",e.type),g(),O(e.type),g(2),O(Et(9,7,e.timestamp,"HH:mm:ss.SSS")),g(2),b(i.isStepEvent(e.type)?10:-1),g(),b(i.isStepEvent(e.type)?-1:11)}}function t5(n,t){if(n&1&&(l(0,"div",12),G(1,e5,12,13,"div",14,Ie),d()),n&2){let e=m();g(),q(e.agentService.sessionLogs())}}var Zd=class n{agentService=D(It);onSessionChange(t){let o=t.target.value;o&&this.agentService.selectSession(o,!0)}isStepEvent(t){return t==="step_recorded"||t==="step_updated"}isShortContent(t){return t?(typeof t=="string"?t:JSON.stringify(t)).length<200:!0}formatJson(t){if(typeof t=="string")try{return JSON.stringify(JSON.parse(t),null,2)}catch(e){return t}return JSON.stringify(t,null,2)}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-legacy-agent-stream"]],decls:20,vars:3,consts:[[1,"agent-stream-container"],[1,"stream-header"],[1,"header-title"],[1,"subtitle"],[1,"session-selector-wrapper"],["for","session-select"],["id","session-select",1,"session-dropdown",3,"change","value"],["value","","disabled","","selected",""],[3,"value"],[1,"btn-refresh",3,"click"],[1,"stream-logs-content"],[1,"empty-state"],[1,"logs-list"],[1,"empty-icon"],[1,"log-card",3,"ngClass"],[1,"log-card-header"],[1,"badge-group"],[1,"index-badge"],[1,"type-badge",3,"ngClass"],[1,"timestamp"],[1,"log-card-body","step-body"],[1,"log-card-body","raw-body"],[1,"step-summary-section"],[1,"thinking-details"],[1,"planning-details"],[1,"tool-details",3,"open"],[1,"result-details"],[1,"section-title"],[1,"summary-bubble"],[1,"details-summary"],[1,"details-content","thinking-text"],[1,"details-content","planning-text"],[1,"details-content"],[1,"json-code"],[1,"raw-details",3,"open"]],template:function(e,o){e&1&&(l(0,"div",0)(1,"header",1)(2,"div",2)(3,"h2"),u(4,"\u{1F463} Real-time Agent Stream (Legacy Card View)"),d(),l(5,"p",3),u(6,"Select a session to monitor raw agent activity"),d()(),l(7,"div",4)(8,"label",5),u(9,"Active Session:"),d(),l(10,"select",6),k("change",function(r){return o.onSessionChange(r)}),l(11,"option",7),u(12,"-- Select a Session --"),d(),G(13,G4,3,7,"option",8,Ie),d(),l(15,"button",9),k("click",function(){return o.agentService.fetchSessions()}),u(16,"\u{1F504} Refresh"),d()()(),l(17,"main",10),M(18,q4,7,0,"div",11),M(19,t5,3,0,"div",12),d()()),e&2&&(g(10),R("value",o.agentService.currentSessionId()||""),g(3),q(o.agentService.sessions()),g(5),b(o.agentService.sessionLogs().length===0?18:-1),g(),b(o.agentService.sessionLogs().length>0?19:-1))},dependencies:[No,ki,Qt,cP,dP,Ei],styles:["[_nghost-%COMP%]{display:flex;flex-direction:column;height:100%;width:100%;overflow:hidden}.agent-stream-container[_ngcontent-%COMP%]{display:flex;flex-direction:column;height:100%;background:#fff6;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);box-sizing:border-box}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center;padding:20px 24px;border-bottom:1px solid rgba(0,0,0,.08);background:#fff9;flex-wrap:wrap;gap:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] h2[_ngcontent-%COMP%]{margin:0 0 4px;font-size:20px;font-weight:700;color:#1f2937;letter-spacing:-.5px}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .header-title[_ngcontent-%COMP%] .subtitle[_ngcontent-%COMP%]{margin:0;font-size:13px;color:#4b5563}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%]{display:flex;align-items:center;gap:12px}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] label[_ngcontent-%COMP%]{font-size:13.5px;font-weight:600;color:#1f2937}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] .session-dropdown[_ngcontent-%COMP%]{padding:8px 12px;border:1px solid rgba(0,0,0,.12);border-radius:8px;font-size:13.5px;background-color:#fff;outline:none;color:#1f2937;min-width:250px;max-width:400px;cursor:pointer;transition:border-color .2s}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] .session-dropdown[_ngcontent-%COMP%]:focus{border-color:#3b82f6}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] .btn-refresh[_ngcontent-%COMP%]{padding:8px 14px;background:#fff;border:1px solid rgba(0,0,0,.12);border-radius:8px;font-size:13px;font-weight:600;color:#1f2937;cursor:pointer;display:flex;align-items:center;gap:6px;transition:background .2s,transform .1s}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] .btn-refresh[_ngcontent-%COMP%]:hover{background:#f3f4f6}.agent-stream-container[_ngcontent-%COMP%] .stream-header[_ngcontent-%COMP%] .session-selector-wrapper[_ngcontent-%COMP%] .btn-refresh[_ngcontent-%COMP%]:active{transform:scale(.98)}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]{flex:1;overflow-y:auto;padding:24px;background:#f9fafb4d}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar{width:6px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-track{background:transparent}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#00000026;border-radius:10px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#00000040}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%]{height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:40px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] .empty-icon[_ngcontent-%COMP%]{font-size:48px;margin-bottom:16px;opacity:.8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] h3[_ngcontent-%COMP%]{margin:0 0 8px;font-size:18px;font-weight:600;color:#1f2937}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .empty-state[_ngcontent-%COMP%] p[_ngcontent-%COMP%]{margin:0;font-size:14px;color:#4b5563;max-width:320px;line-height:1.5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%]{background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:12px;padding:16px 20px;box-shadow:0 4px 6px -1px #00000003,0 2px 4px -1px #00000002;display:flex;flex-direction:column;gap:12px;transition:transform .2s,box-shadow .2s}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%]:hover{transform:translateY(-1px);box-shadow:0 10px 15px -3px #00000005,0 4px 6px -2px #00000003}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%]{display:flex;justify-content:space-between;align-items:center}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%]{display:flex;align-items:center;gap:8px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .index-badge[_ngcontent-%COMP%]{font-size:11px;font-weight:700;color:#9ca3af;font-family:monospace}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge[_ngcontent-%COMP%]{font-size:10px;font-weight:800;text-transform:uppercase;padding:2.5px 7px;border-radius:9999px;letter-spacing:.5px;font-family:monospace}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge.llm_stream[_ngcontent-%COMP%]{background:#e0f2fe;color:#0369a1}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge.trace_recorded[_ngcontent-%COMP%]{background:#f3e8ff;color:#6b21a8}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge.step_recorded[_ngcontent-%COMP%]{background:#ecfdf5;color:#047857}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge.step_updated[_ngcontent-%COMP%]{background:#fef3c7;color:#b45309}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .badge-group[_ngcontent-%COMP%] .type-badge.background_tasks_updated[_ngcontent-%COMP%]{background:#fff1f2;color:#be123c}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-header[_ngcontent-%COMP%] .timestamp[_ngcontent-%COMP%]{font-size:11.5px;color:#9ca3af;font-family:monospace}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%]{display:flex;flex-direction:column;gap:16px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] .step-summary-section[_ngcontent-%COMP%] .section-title[_ngcontent-%COMP%]{font-size:11px;font-weight:700;color:#4b5563;margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] .step-summary-section[_ngcontent-%COMP%] .summary-bubble[_ngcontent-%COMP%]{background:#f1f5f9;border:1px solid rgba(0,0,0,.05);border-left:4px solid #475569;border-radius:8px;padding:12px 16px;font-size:13.5px;line-height:1.5;color:#1e293b}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%]{border:1px solid rgba(0,0,0,.06);border-radius:8px;background:#fafafa;overflow:hidden;transition:border-color .2s}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[open][_ngcontent-%COMP%]{border-color:#0000001f}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[open][_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{border-bottom:1px solid rgba(0,0,0,.06);background:#00000005}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{padding:10px 14px;font-size:12.5px;font-weight:700;color:#4b5563;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;background:#fff;transition:background .2s}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]:hover{background:#00000003}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-content[_ngcontent-%COMP%]{padding:14px;font-size:13px;line-height:1.6;color:#334155}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-content.thinking-text[_ngcontent-%COMP%]{background:#f0fdf4;color:#14532d;white-space:pre-wrap;font-family:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-content.planning-text[_ngcontent-%COMP%]{background:#f8fafc;color:#334155;white-space:pre-wrap;font-family:inherit}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.thinking-details[_ngcontent-%COMP%]{border-left:4px solid #16a34a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.thinking-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#16a34a}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.planning-details[_ngcontent-%COMP%]{border-left:4px solid #4f46e5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.planning-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#4f46e5}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.tool-details[_ngcontent-%COMP%]{border-left:4px solid #2563eb}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.tool-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#2563eb}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.result-details[_ngcontent-%COMP%]{border-left:4px solid #0891b2}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.step-body[_ngcontent-%COMP%] details.result-details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{color:#0891b2}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.raw-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%]{border:1px solid rgba(0,0,0,.06);border-radius:8px;overflow:hidden}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.raw-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-summary[_ngcontent-%COMP%]{padding:10px 14px;font-size:12px;font-weight:600;color:#4b5563;cursor:pointer;outline:none;background:#fff}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body.raw-body[_ngcontent-%COMP%] details[_ngcontent-%COMP%] .details-content[_ngcontent-%COMP%]{padding:12px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body[_ngcontent-%COMP%] .json-code[_ngcontent-%COMP%]{margin:0;background:#1e293b;color:#f8fafc;padding:14px 18px;border-radius:8px;font-family:Fira Code,Roboto Mono,monospace;font-size:12.5px;line-height:1.5;overflow-x:auto;white-space:pre-wrap;word-break:break-all}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body[_ngcontent-%COMP%] .json-code[_ngcontent-%COMP%]::-webkit-scrollbar{height:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body[_ngcontent-%COMP%] .json-code[_ngcontent-%COMP%]::-webkit-scrollbar-thumb{background:#fff3;border-radius:4px}.agent-stream-container[_ngcontent-%COMP%] .stream-logs-content[_ngcontent-%COMP%] .logs-list[_ngcontent-%COMP%] .log-card[_ngcontent-%COMP%] .log-card-body[_ngcontent-%COMP%] .json-code[_ngcontent-%COMP%]::-webkit-scrollbar-thumb:hover{background:#ffffff4d}"],changeDetection:1})};var Qd=class n{rightPanelWidth=typeof window<"u"?Math.round(window.innerWidth/3):450;isDragging=!1;onDragStart(t){this.isDragging=!0,t.preventDefault()}onMouseMove(t){if(!this.isDragging)return;let e=window.innerWidth-t.clientX,o=300,i=window.innerWidth-300;e>=o&&e<=i&&(this.rightPanelWidth=e)}onMouseUp(){this.isDragging=!1}static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-legacy-workspace"]],hostBindings:function(e,o){e&1&&k("mousemove",function(r){return o.onMouseMove(r)},hl)("mouseup",function(){return o.onMouseUp()},hl)},decls:11,vars:4,consts:[[1,"workspace-container"],[1,"aurora-background"],[1,"glow","glow-1"],[1,"glow","glow-2"],[1,"glow","glow-3"],[1,"left-panel","glass-panel"],[1,"resizer",3,"mousedown"],[1,"resizer-handle"],[1,"right-panel"]],template:function(e,o){e&1&&(l(0,"div",0)(1,"div",1),$(2,"div",2)(3,"div",3)(4,"div",4),d(),l(5,"main",5),$(6,"app-legacy-agent-stream"),d(),l(7,"div",6),k("mousedown",function(r){return o.onDragStart(r)}),$(8,"div",7),d(),l(9,"aside",8),$(10,"app-chat-interface"),d()()),e&2&&(w("is-resizing",o.isDragging),g(9),Se("width",o.rightPanelWidth,"px"))},dependencies:[Zd,qr],styles:[".workspace-container[_ngcontent-%COMP%]{position:relative;display:flex;height:100vh;width:100vw;background-color:#e2e5ea;overflow:hidden}.workspace-container.is-resizing[_ngcontent-%COMP%]{cursor:col-resize;-webkit-user-select:none;user-select:none}.workspace-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%]{position:absolute;top:0;left:0;width:100%;height:100%;z-index:0;overflow:hidden;background:linear-gradient(135deg,#e2e5ea,#cfd6df)}.workspace-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow[_ngcontent-%COMP%]{position:absolute;border-radius:50%;filter:blur(90px);opacity:.5;animation:_ngcontent-%COMP%_aurora-drift 22s infinite alternate ease-in-out}.workspace-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-1[_ngcontent-%COMP%]{width:55vw;height:55vh;background:#5e72e440;top:-15%;left:-5%;animation-delay:0s}.workspace-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-2[_ngcontent-%COMP%]{width:45vw;height:45vh;background:#2dce8933;bottom:-10%;left:30%;animation-delay:-5s}.workspace-container[_ngcontent-%COMP%] .aurora-background[_ngcontent-%COMP%] .glow-3[_ngcontent-%COMP%]{width:50vw;height:60vh;background:#ce68ac26;top:20%;right:-10%;animation-delay:-10s}.workspace-container[_ngcontent-%COMP%] .glass-panel[_ngcontent-%COMP%]{position:relative;z-index:1;background:#ffffff73;backdrop-filter:blur(24px) saturate(120%);-webkit-backdrop-filter:blur(24px) saturate(120%);border:1px solid rgba(255,255,255,.6);box-shadow:0 8px 32px #1f26870d}.workspace-container[_ngcontent-%COMP%] .left-panel[_ngcontent-%COMP%]{flex:1;display:flex;flex-direction:column;overflow:hidden;background:#ffffff59;border-right:1px solid rgba(255,255,255,.8);border-left:none;border-top:none;border-bottom:none}.workspace-container[_ngcontent-%COMP%] .right-panel[_ngcontent-%COMP%]{min-width:300px;flex-shrink:0;padding:0;border:none;background-color:#fff;box-shadow:-6px 0 24px #00000008;height:100%;display:flex;flex-direction:column;box-sizing:border-box;z-index:2}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]{width:12px;cursor:col-resize;display:flex;align-items:center;justify-content:center;z-index:10;transition:background .2s ease;margin:0 -6px}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]:hover, .workspace-container.is-resizing[_ngcontent-%COMP%] .workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]{background:#ffffff26}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%]:hover .resizer-handle[_ngcontent-%COMP%], .workspace-container.is-resizing[_ngcontent-%COMP%] .workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%] .resizer-handle[_ngcontent-%COMP%]{background:#5e72e4cc;height:60px}.workspace-container[_ngcontent-%COMP%] .resizer[_ngcontent-%COMP%] .resizer-handle[_ngcontent-%COMP%]{width:4px;height:40px;background:#00000026;border-radius:4px;transition:all .2s ease}@keyframes _ngcontent-%COMP%_aurora-drift{0%{transform:translate(0) scale(1)}50%{transform:translate(5%,3%) scale(1.05)}to{transform:translate(-2%,-5%) scale(.95)}}"],changeDetection:1})};var iO=[{path:"",component:qd},{path:"workspace",component:Yd},{path:"check",component:Qd},{path:"**",redirectTo:""}];var rO={providers:[qM({eventCoalescing:!0}),Wf(iO),_f(hf())]};var n5=()=>({exact:!0}),Jd=class n{static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-nav-switcher"]],decls:11,vars:2,consts:[["aria-label","Main Navigation",1,"floating-nav-switcher"],["routerLink","/","routerLinkActive","active","title","Return to Home Launcher to start a new task",1,"nav-tab-btn",3,"routerLinkActiveOptions"],[1,"material-symbols-outlined","tab-icon"],[1,"tab-label"],["routerLink","/workspace","routerLinkActive","active","title","Open Workspace",1,"nav-tab-btn"]],template:function(e,o){e&1&&(l(0,"nav",0)(1,"a",1)(2,"span",2),u(3,"add_task"),d(),l(4,"span",3),u(5,"New / Home"),d()(),l(6,"a",4)(7,"span",2),u(8,"space_dashboard"),d(),l(9,"span",3),u(10,"Workspace"),d()()()),e&2&&(g(),R("routerLinkActiveOptions",za(1,n5)))},dependencies:[Lr,qf],styles:[".floating-nav-switcher[_ngcontent-%COMP%]{position:fixed;top:12px;left:24px;z-index:1100;height:36px;box-sizing:border-box;display:inline-flex;align-items:center;background:#ffffffb8;backdrop-filter:blur(16px) saturate(180%);-webkit-backdrop-filter:blur(16px) saturate(180%);border:1px solid rgba(226,232,240,.85);border-radius:18px;padding:3px;gap:3px;box-shadow:0 2px 8px -1px #0f172a0f,0 1px 2px #00000005,inset 0 1px 1px #ffffffe6;-webkit-user-select:none;user-select:none;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:background .25s cubic-bezier(.4,0,.2,1),box-shadow .25s cubic-bezier(.4,0,.2,1),border-color .25s cubic-bezier(.4,0,.2,1)}.floating-nav-switcher[_ngcontent-%COMP%]:hover{background:#ffffffd9;box-shadow:0 4px 14px -2px #0f172a1a,0 2px 4px #00000008,inset 0 1px 1px #fff;border-color:#cbd5e1e6}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%]{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:30px;padding:0 14px;box-sizing:border-box;border-radius:15px;font-size:13px;font-weight:600;color:#475569;text-decoration:none;cursor:pointer;line-height:1;transition:background .2s cubic-bezier(.4,0,.2,1),color .2s cubic-bezier(.4,0,.2,1),box-shadow .2s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1);outline:none;white-space:nowrap}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%]{font-size:16px;width:16px;height:16px;display:inline-flex;align-items:center;justify-content:center;color:#64748b;transition:color .15s ease,transform .15s ease}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%]{color:#475569;transition:color .15s ease}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%]:hover:not(.active){background:#ffffffb3;color:#0f172a}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%]:hover:not(.active) .tab-icon[_ngcontent-%COMP%]{color:#1a73e8}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%]:hover:not(.active) .tab-label[_ngcontent-%COMP%]{color:#0f172a}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn.active[_ngcontent-%COMP%]{background:#1a73e8f2!important;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);color:#fff!important;font-weight:700;box-shadow:0 2px 8px #1a73e859,inset 0 1px 1px #ffffff59}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn.active[_ngcontent-%COMP%] .tab-icon[_ngcontent-%COMP%], .floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn.active[_ngcontent-%COMP%] .tab-label[_ngcontent-%COMP%], .floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn.active[_ngcontent-%COMP%] span[_ngcontent-%COMP%]{color:#fff!important}.floating-nav-switcher[_ngcontent-%COMP%] .nav-tab-btn[_ngcontent-%COMP%]:active{transform:scale(.97)}"],changeDetection:1})};var Xd=class n{title="frontend";static \u0275fac=function(e){return new(e||n)};static \u0275cmp=Ge({type:n,selectors:[["app-root"]],decls:2,vars:0,template:function(e,o){e&1&&$(0,"app-nav-switcher")(1,"router-outlet")},dependencies:[hs,Jd],encapsulation:2,changeDetection:1})};df(Xd,rO).catch(n=>console.error(n)); diff --git a/artemis/resources/showcase_ui/polyfills-5CFQRCPP.js b/artemis/resources/showcase_ui/polyfills-5CFQRCPP.js new file mode 100644 index 0000000..b237b5e --- /dev/null +++ b/artemis/resources/showcase_ui/polyfills-5CFQRCPP.js @@ -0,0 +1,2 @@ +var ce=globalThis;function te(t){return(ce.__Zone_symbol_prefix||"__zone_symbol__")+t}function ht(){let t=ce.performance;function n(I){t&&t.mark&&t.mark(I)}function a(I,s){t&&t.measure&&t.measure(I,s)}n("Zone");class e{static __symbol__=te;static assertZonePatched(){if(ce.Promise!==S.ZoneAwarePromise)throw new Error("Zone.js has detected that ZoneAwarePromise `(window|global).Promise` has been overwritten.\nMost likely cause is that a Promise polyfill has been loaded after Zone.js (Polyfilling Promise api is not necessary when zone.js is loaded. If you must load one, do so before loading zone.js.)")}static get root(){let s=e.current;for(;s.parent;)s=s.parent;return s}static get current(){return b.zone}static get currentTask(){return D}static __load_patch(s,i,r=!1){if(S.hasOwnProperty(s)){let E=ce[te("forceDuplicateZoneCheck")]===!0;if(!r&&E)throw Error("Already loaded patch: "+s)}else if(!ce["__Zone_disable_"+s]){let E="Zone:"+s;n(E),S[s]=i(ce,e,R),a(E,E)}}get parent(){return this._parent}get name(){return this._name}_parent;_name;_properties;_zoneDelegate;constructor(s,i){this._parent=s,this._name=i?i.name||"unnamed":"",this._properties=i&&i.properties||{},this._zoneDelegate=new f(this,this._parent&&this._parent._zoneDelegate,i)}get(s){let i=this.getZoneWith(s);if(i)return i._properties[s]}getZoneWith(s){let i=this;for(;i;){if(i._properties.hasOwnProperty(s))return i;i=i._parent}return null}fork(s){if(!s)throw new Error("ZoneSpec required!");return this._zoneDelegate.fork(this,s)}wrap(s,i){if(typeof s!="function")throw new Error("Expecting function got: "+s);let r=this._zoneDelegate.intercept(this,s,i),E=this;return function(){return E.runGuarded(r,this,arguments,i)}}run(s,i,r,E){b={parent:b,zone:this};try{return this._zoneDelegate.invoke(this,s,i,r,E)}finally{b=b.parent}}runGuarded(s,i=null,r,E){b={parent:b,zone:this};try{try{return this._zoneDelegate.invoke(this,s,i,r,E)}catch(x){if(this._zoneDelegate.handleError(this,x))throw x}}finally{b=b.parent}}runTask(s,i,r){if(s.zone!=this)throw new Error("A task can only be run in the zone of creation! (Creation: "+(s.zone||J).name+"; Execution: "+this.name+")");let E=s,{type:x,data:{isPeriodic:ee=!1,isRefreshable:M=!1}={}}=s;if(s.state===q&&(x===U||x===k))return;let he=s.state!=A;he&&E._transitionTo(A,d);let _e=D;D=E,b={parent:b,zone:this};try{x==k&&s.data&&!ee&&!M&&(s.cancelFn=void 0);try{return this._zoneDelegate.invokeTask(this,E,i,r)}catch(Q){if(this._zoneDelegate.handleError(this,Q))throw Q}}finally{let Q=s.state;if(Q!==q&&Q!==X)if(x==U||ee||M&&Q===p)he&&E._transitionTo(d,A,p);else{let Te=E._zoneDelegates;this._updateTaskCount(E,-1),he&&E._transitionTo(q,A,q),M&&(E._zoneDelegates=Te)}b=b.parent,D=_e}}scheduleTask(s){if(s.zone&&s.zone!==this){let r=this;for(;r;){if(r===s.zone)throw Error(`can not reschedule task to ${this.name} which is descendants of the original zone ${s.zone.name}`);r=r.parent}}s._transitionTo(p,q);let i=[];s._zoneDelegates=i,s._zone=this;try{s=this._zoneDelegate.scheduleTask(this,s)}catch(r){throw s._transitionTo(X,p,q),this._zoneDelegate.handleError(this,r),r}return s._zoneDelegates===i&&this._updateTaskCount(s,1),s.state==p&&s._transitionTo(d,p),s}scheduleMicroTask(s,i,r,E){return this.scheduleTask(new g(F,s,i,r,E,void 0))}scheduleMacroTask(s,i,r,E,x){return this.scheduleTask(new g(k,s,i,r,E,x))}scheduleEventTask(s,i,r,E,x){return this.scheduleTask(new g(U,s,i,r,E,x))}cancelTask(s){if(s.zone!=this)throw new Error("A task can only be cancelled in the zone of creation! (Creation: "+(s.zone||J).name+"; Execution: "+this.name+")");if(!(s.state!==d&&s.state!==A)){s._transitionTo(V,d,A);try{this._zoneDelegate.cancelTask(this,s)}catch(i){throw s._transitionTo(X,V),this._zoneDelegate.handleError(this,i),i}return this._updateTaskCount(s,-1),s._transitionTo(q,V),s.runCount=-1,s}}_updateTaskCount(s,i){let r=s._zoneDelegates;i==-1&&(s._zoneDelegates=null);for(let E=0;EI.hasTask(i,r),onScheduleTask:(I,s,i,r)=>I.scheduleTask(i,r),onInvokeTask:(I,s,i,r,E,x)=>I.invokeTask(i,r,E,x),onCancelTask:(I,s,i,r)=>I.cancelTask(i,r)};class f{get zone(){return this._zone}_zone;_taskCounts={microTask:0,macroTask:0,eventTask:0};_parentDelegate;_forkDlgt;_forkZS;_forkCurrZone;_interceptDlgt;_interceptZS;_interceptCurrZone;_invokeDlgt;_invokeZS;_invokeCurrZone;_handleErrorDlgt;_handleErrorZS;_handleErrorCurrZone;_scheduleTaskDlgt;_scheduleTaskZS;_scheduleTaskCurrZone;_invokeTaskDlgt;_invokeTaskZS;_invokeTaskCurrZone;_cancelTaskDlgt;_cancelTaskZS;_cancelTaskCurrZone;_hasTaskDlgt;_hasTaskDlgtOwner;_hasTaskZS;_hasTaskCurrZone;constructor(s,i,r){this._zone=s,this._parentDelegate=i,this._forkZS=r&&(r&&r.onFork?r:i._forkZS),this._forkDlgt=r&&(r.onFork?i:i._forkDlgt),this._forkCurrZone=r&&(r.onFork?this._zone:i._forkCurrZone),this._interceptZS=r&&(r.onIntercept?r:i._interceptZS),this._interceptDlgt=r&&(r.onIntercept?i:i._interceptDlgt),this._interceptCurrZone=r&&(r.onIntercept?this._zone:i._interceptCurrZone),this._invokeZS=r&&(r.onInvoke?r:i._invokeZS),this._invokeDlgt=r&&(r.onInvoke?i:i._invokeDlgt),this._invokeCurrZone=r&&(r.onInvoke?this._zone:i._invokeCurrZone),this._handleErrorZS=r&&(r.onHandleError?r:i._handleErrorZS),this._handleErrorDlgt=r&&(r.onHandleError?i:i._handleErrorDlgt),this._handleErrorCurrZone=r&&(r.onHandleError?this._zone:i._handleErrorCurrZone),this._scheduleTaskZS=r&&(r.onScheduleTask?r:i._scheduleTaskZS),this._scheduleTaskDlgt=r&&(r.onScheduleTask?i:i._scheduleTaskDlgt),this._scheduleTaskCurrZone=r&&(r.onScheduleTask?this._zone:i._scheduleTaskCurrZone),this._invokeTaskZS=r&&(r.onInvokeTask?r:i._invokeTaskZS),this._invokeTaskDlgt=r&&(r.onInvokeTask?i:i._invokeTaskDlgt),this._invokeTaskCurrZone=r&&(r.onInvokeTask?this._zone:i._invokeTaskCurrZone),this._cancelTaskZS=r&&(r.onCancelTask?r:i._cancelTaskZS),this._cancelTaskDlgt=r&&(r.onCancelTask?i:i._cancelTaskDlgt),this._cancelTaskCurrZone=r&&(r.onCancelTask?this._zone:i._cancelTaskCurrZone),this._hasTaskZS=null,this._hasTaskDlgt=null,this._hasTaskDlgtOwner=null,this._hasTaskCurrZone=null;let E=r&&r.onHasTask,x=i&&i._hasTaskZS;(E||x)&&(this._hasTaskZS=E?r:c,this._hasTaskDlgt=i,this._hasTaskDlgtOwner=this,this._hasTaskCurrZone=this._zone,r.onScheduleTask||(this._scheduleTaskZS=c,this._scheduleTaskDlgt=i,this._scheduleTaskCurrZone=this._zone),r.onInvokeTask||(this._invokeTaskZS=c,this._invokeTaskDlgt=i,this._invokeTaskCurrZone=this._zone),r.onCancelTask||(this._cancelTaskZS=c,this._cancelTaskDlgt=i,this._cancelTaskCurrZone=this._zone))}fork(s,i){return this._forkZS?this._forkZS.onFork(this._forkDlgt,this.zone,s,i):new e(s,i)}intercept(s,i,r){return this._interceptZS?this._interceptZS.onIntercept(this._interceptDlgt,this._interceptCurrZone,s,i,r):i}invoke(s,i,r,E,x){return this._invokeZS?this._invokeZS.onInvoke(this._invokeDlgt,this._invokeCurrZone,s,i,r,E,x):i.apply(r,E)}handleError(s,i){return this._handleErrorZS?this._handleErrorZS.onHandleError(this._handleErrorDlgt,this._handleErrorCurrZone,s,i):!0}scheduleTask(s,i){let r=i;if(this._scheduleTaskZS)this._hasTaskZS&&r._zoneDelegates.push(this._hasTaskDlgtOwner),r=this._scheduleTaskZS.onScheduleTask(this._scheduleTaskDlgt,this._scheduleTaskCurrZone,s,i),r||(r=i);else if(i.scheduleFn)i.scheduleFn(i);else if(i.type==F)z(i);else throw new Error("Task is missing scheduleFn.");return r}invokeTask(s,i,r,E){return this._invokeTaskZS?this._invokeTaskZS.onInvokeTask(this._invokeTaskDlgt,this._invokeTaskCurrZone,s,i,r,E):i.callback.apply(r,E)}cancelTask(s,i){let r;if(this._cancelTaskZS)r=this._cancelTaskZS.onCancelTask(this._cancelTaskDlgt,this._cancelTaskCurrZone,s,i);else{if(!i.cancelFn)throw Error("Task is not cancelable");r=i.cancelFn(i)}return r}hasTask(s,i){try{this._hasTaskZS&&this._hasTaskZS.onHasTask(this._hasTaskDlgt,this._hasTaskCurrZone,s,i)}catch(r){this.handleError(s,r)}}_updateTaskCount(s,i){let r=this._taskCounts,E=r[s],x=r[s]=E+i;if(x<0)throw new Error("More tasks executed then were scheduled.");if(E==0||x==0){let ee={microTask:r.microTask>0,macroTask:r.macroTask>0,eventTask:r.eventTask>0,change:s};this.hasTask(this._zone,ee)}}}class g{type;source;invoke;callback;data;scheduleFn;cancelFn;_zone=null;runCount=0;_zoneDelegates=null;_state="notScheduled";constructor(s,i,r,E,x,ee){if(this.type=s,this.source=i,this.data=E,this.scheduleFn=x,this.cancelFn=ee,!r)throw new Error("callback is not defined");this.callback=r;let M=this;s===U&&E&&E.useG?this.invoke=g.invokeTask:this.invoke=function(){return g.invokeTask.call(ce,M,this,arguments)}}static invokeTask(s,i,r){s||(s=this),K++;try{return s.runCount++,s.zone.runTask(s,i,r)}finally{K==1&&$(),K--}}get zone(){return this._zone}get state(){return this._state}cancelScheduleRequest(){this._transitionTo(q,p)}_transitionTo(s,i,r){if(this._state===i||this._state===r)this._state=s,s==q&&(this._zoneDelegates=null);else throw new Error(`${this.type} '${this.source}': can not transition to '${s}', expecting state '${i}'${r?" or '"+r+"'":""}, was '${this._state}'.`)}toString(){return this.data&&typeof this.data.handleId<"u"?this.data.handleId.toString():Object.prototype.toString.call(this)}toJSON(){return{type:this.type,state:this.state,source:this.source,zone:this.zone.name,runCount:this.runCount}}}let T=te("setTimeout"),y=te("Promise"),w=te("then"),_=[],P=!1,L;function H(I){if(L||ce[y]&&(L=ce[y].resolve(0)),L){let s=L[w];s||(s=L.then),s.call(L,I)}else ce[T](I,0)}function z(I){K===0&&_.length===0&&H($),I&&_.push(I)}function $(){if(!P){for(P=!0;_.length;){let I=_;_=[];for(let s=0;sb,onUnhandledError:W,microtaskDrainDone:W,scheduleMicroTask:z,showUncaughtError:()=>!e[te("ignoreConsoleErrorUncaughtError")],patchEventTarget:()=>[],patchOnProperties:W,patchMethod:()=>W,bindArguments:()=>[],patchThen:()=>W,patchMacroTask:()=>W,patchEventPrototype:()=>W,isIEOrEdge:()=>!1,getGlobalObjects:()=>{},ObjectDefineProperty:()=>W,ObjectGetOwnPropertyDescriptor:()=>{},ObjectCreate:()=>{},ArraySlice:()=>[],patchClass:()=>W,wrapWithCurrentZone:()=>W,filterProperties:()=>[],attachOriginToPatched:()=>W,_redefineProperty:()=>W,patchCallbacks:()=>W,nativeScheduleMicroTask:H},b={parent:null,zone:new e(null,null)},D=null,K=0;function W(){}return a("Zone","Zone"),e}function dt(){let t=globalThis,n=t[te("forceDuplicateZoneCheck")]===!0;if(t.Zone&&(n||typeof t.Zone.__symbol__!="function"))throw new Error("Zone already loaded.");return t.Zone??=ht(),t.Zone}var pe=Object.getOwnPropertyDescriptor,Me=Object.defineProperty,Ae=Object.getPrototypeOf,_t=Object.create,Tt=Array.prototype.slice,je="addEventListener",He="removeEventListener",Ne=te(je),Ze=te(He),ae="true",le="false",ve=te("");function Ve(t,n){return Zone.current.wrap(t,n)}function xe(t,n,a,e,c){return Zone.current.scheduleMacroTask(t,n,a,e,c)}var j=te,we=typeof window<"u",be=we?window:void 0,Y=we&&be||globalThis,Et="removeAttribute";function Fe(t,n){for(let a=t.length-1;a>=0;a--)typeof t[a]=="function"&&(t[a]=Ve(t[a],n+"_"+a));return t}function gt(t,n){let a=t.constructor.name;for(let e=0;e{let y=function(){return T.apply(this,Fe(arguments,a+"."+c))};return fe(y,T),y})(f)}}}function et(t){return t?t.writable===!1?!1:!(typeof t.get=="function"&&typeof t.set>"u"):!0}var tt=typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope,De=!("nw"in Y)&&typeof Y.process<"u"&&Y.process.toString()==="[object process]",Ge=!De&&!tt&&!!(we&&be.HTMLElement),nt=typeof Y.process<"u"&&Y.process.toString()==="[object process]"&&!tt&&!!(we&&be.HTMLElement),Ce={},kt=j("enable_beforeunload"),Xe=function(t){if(t=t||Y.event,!t)return;let n=Ce[t.type];n||(n=Ce[t.type]=j("ON_PROPERTY"+t.type));let a=this||t.target||Y,e=a[n],c;if(Ge&&a===be&&t.type==="error"){let f=t;c=e&&e.call(this,f.message,f.filename,f.lineno,f.colno,f.error),c===!0&&t.preventDefault()}else c=e&&e.apply(this,arguments),t.type==="beforeunload"&&Y[kt]&&typeof c=="string"?t.returnValue=c:c!=null&&!c&&t.preventDefault();return c};function Ye(t,n,a){let e=pe(t,n);if(!e&&a&&pe(a,n)&&(e={enumerable:!0,configurable:!0}),!e||!e.configurable)return;let c=j("on"+n+"patched");if(t.hasOwnProperty(c)&&t[c])return;delete e.writable,delete e.value;let f=e.get,g=e.set,T=n.slice(2),y=Ce[T];y||(y=Ce[T]=j("ON_PROPERTY"+T)),e.set=function(w){let _=this;if(!_&&t===Y&&(_=Y),!_)return;typeof _[y]=="function"&&_.removeEventListener(T,Xe),g?.call(_,null),_[y]=w,typeof w=="function"&&_.addEventListener(T,Xe,!1)},e.get=function(){let w=this;if(!w&&t===Y&&(w=Y),!w)return null;let _=w[y];if(_)return _;if(f){let P=f.call(this);if(P)return e.set.call(this,P),typeof w[Et]=="function"&&w.removeAttribute(n),P}return null},Me(t,n,e),t[c]=!0}function rt(t,n,a){if(n)for(let e=0;efunction(g,T){let y=a(g,T);return y.cbIdx>=0&&typeof T[y.cbIdx]=="function"?xe(y.name,T[y.cbIdx],y,c):f.apply(g,T)})}function fe(t,n){t[j("OriginalDelegate")]=n}var $e=!1,Le=!1;function yt(){if($e)return Le;$e=!0;try{let t=be.navigator.userAgent;(t.indexOf("MSIE ")!==-1||t.indexOf("Trident/")!==-1||t.indexOf("Edge/")!==-1)&&(Le=!0)}catch{}return Le}function Je(t){return typeof t=="function"}function Ke(t){return typeof t=="number"}var pt={useG:!0},ne={},ot={},st=new RegExp("^"+ve+"(\\w+)(true|false)$"),it=j("propagationStopped");function ct(t,n){let a=(n?n(t):t)+le,e=(n?n(t):t)+ae,c=ve+a,f=ve+e;ne[t]={},ne[t][le]=c,ne[t][ae]=f}function vt(t,n,a,e){let c=e&&e.add||je,f=e&&e.rm||He,g=e&&e.listeners||"eventListeners",T=e&&e.rmAll||"removeAllListeners",y=j(c),w="."+c+":",_="prependListener",P="."+_+":",L=function(p,d,A){if(p.isRemoved)return;let V=p.callback;typeof V=="object"&&V.handleEvent&&(p.callback=k=>V.handleEvent(k),p.originalDelegate=V);let X;try{p.invoke(p,d,[A])}catch(k){X=k}let F=p.options;if(F&&typeof F=="object"&&F.once){let k=p.originalDelegate?p.originalDelegate:p.callback;d[f].call(d,A.type,k,F)}return X};function H(p,d,A){if(d=d||t.event,!d)return;let V=p||d.target||t,X=V[ne[d.type][A?ae:le]];if(X){let F=[];if(X.length===1){let k=L(X[0],V,d);k&&F.push(k)}else{let k=X.slice();for(let U=0;U{throw U})}}}let z=function(p){return H(this,p,!1)},$=function(p){return H(this,p,!0)};function J(p,d){if(!p)return!1;let A=!0;d&&d.useG!==void 0&&(A=d.useG);let V=d&&d.vh,X=!0;d&&d.chkDup!==void 0&&(X=d.chkDup);let F=!1;d&&d.rt!==void 0&&(F=d.rt);let k=p;for(;k&&!k.hasOwnProperty(c);)k=Ae(k);if(!k&&p[c]&&(k=p),!k||k[y])return!1;let U=d&&d.eventNameToString,S={},R=k[y]=k[c],b=k[j(f)]=k[f],D=k[j(g)]=k[g],K=k[j(T)]=k[T],W;d&&d.prepend&&(W=k[j(d.prepend)]=k[d.prepend]);function I(o,u){return u?typeof o=="boolean"?{capture:o,passive:!0}:o?typeof o=="object"&&o.passive!==!1?{...o,passive:!0}:o:{passive:!0}:o}let s=function(o){if(!S.isExisting)return R.call(S.target,S.eventName,S.capture?$:z,S.options)},i=function(o){if(!o.isRemoved){let u=ne[o.eventName],v;u&&(v=u[o.capture?ae:le]);let C=v&&o.target[v];if(C){for(let m=0;mre.zone.cancelTask(re);o.call(Ee,"abort",ie,{once:!0}),re.removeAbortListener=()=>Ee.removeEventListener("abort",ie)}if(S.target=null,me&&(me.taskData=null),Be&&(S.options.once=!0),typeof re.options!="boolean"&&(re.options=se),re.target=N,re.capture=Se,re.eventName=Z,B&&(re.originalDelegate=G),O?ge.unshift(re):ge.push(re),m)return N}};return k[c]=l(R,w,ee,M,F),W&&(k[_]=l(W,P,E,M,F,!0)),k[f]=function(){let o=this||t,u=arguments[0];d&&d.transferEventName&&(u=d.transferEventName(u));let v=arguments[2],C=v?typeof v=="boolean"?!0:v.capture:!1,m=arguments[1];if(!m)return b.apply(this,arguments);if(V&&!V(b,m,o,arguments))return;let O=ne[u],N;O&&(N=O[C?ae:le]);let Z=N&&o[N];if(Z)for(let G=0;Gfunction(c,f){c[it]=!0,e&&e.apply(c,f)})}function Pt(t,n){n.patchMethod(t,"queueMicrotask",a=>function(e,c){Zone.current.scheduleMicroTask("queueMicrotask",c[0])})}var Re=j("zoneTask");function ke(t,n,a,e){let c=null,f=null;n+=e,a+=e;let g={};function T(w){let _=w.data;_.args[0]=function(){return w.invoke.apply(this,arguments)};let P=c.apply(t,_.args);return Ke(P)?_.handleId=P:(_.handle=P,_.isRefreshable=Je(P.refresh)),w}function y(w){let{handle:_,handleId:P}=w.data;return f.call(t,_??P)}c=ue(t,n,w=>function(_,P){if(Je(P[0])){let L={isRefreshable:!1,isPeriodic:e==="Interval",delay:e==="Timeout"||e==="Interval"?P[1]||0:void 0,args:P},H=P[0];P[0]=function(){try{return H.apply(this,arguments)}finally{let{handle:A,handleId:V,isPeriodic:X,isRefreshable:F}=L;!X&&!F&&(V?delete g[V]:A&&(A[Re]=null))}};let z=xe(n,P[0],L,T,y);if(!z)return z;let{handleId:$,handle:J,isRefreshable:q,isPeriodic:p}=z.data;if($)g[$]=z;else if(J&&(J[Re]=z,q&&!p)){let d=J.refresh;J.refresh=function(){let{zone:A,state:V}=z;return V==="notScheduled"?(z._state="scheduled",A._updateTaskCount(z,1)):V==="running"&&(z._state="scheduling"),d.call(this)}}return J??$??z}else return w.apply(t,P)}),f=ue(t,a,w=>function(_,P){let L=P[0],H;Ke(L)?(H=g[L],delete g[L]):(H=L?.[Re],H?L[Re]=null:H=L),H?.type?H.cancelFn&&H.zone.cancelTask(H):w.apply(t,P)})}function Rt(t,n){let{isBrowser:a,isMix:e}=n.getGlobalObjects();if(!a&&!e||!t.customElements||!("customElements"in t))return;let c=["connectedCallback","disconnectedCallback","adoptedCallback","attributeChangedCallback","formAssociatedCallback","formDisabledCallback","formResetCallback","formStateRestoreCallback"];n.patchCallbacks(n,t.customElements,"customElements","define",c)}function Ct(t,n){if(Zone[n.symbol("patchEventTarget")])return;let{eventNames:a,zoneSymbolEventNames:e,TRUE_STR:c,FALSE_STR:f,ZONE_SYMBOL_PREFIX:g}=n.getGlobalObjects();for(let y=0;yf.target===t);if(e.length===0)return n;let c=e[0].ignoreProperties;return n.filter(f=>c.indexOf(f)===-1)}function Qe(t,n,a,e){if(!t)return;let c=lt(t,n,a);rt(t,c,e)}function Ie(t){return Object.getOwnPropertyNames(t).filter(n=>n.startsWith("on")&&n.length>2).map(n=>n.substring(2))}function Dt(t,n){if(De&&!nt||Zone[t.symbol("patchEvents")])return;let a=n.__Zone_ignore_on_properties,e=[];if(Ge){let c=window;e=e.concat(["Document","SVGElement","Element","HTMLElement","HTMLBodyElement","HTMLMediaElement","HTMLFrameSetElement","HTMLFrameElement","HTMLIFrameElement","HTMLMarqueeElement","Worker"]);let f=[];Qe(c,Ie(c),a&&a.concat(f),Ae(c))}e=e.concat(["XMLHttpRequest","XMLHttpRequestEventTarget","IDBIndex","IDBRequest","IDBOpenDBRequest","IDBDatabase","IDBTransaction","IDBCursor","WebSocket"]);for(let c=0;c{let a=n[t.__symbol__("legacyPatch")];a&&a()}),t.__load_patch("timers",n=>{let e="clear";ke(n,"set",e,"Timeout"),ke(n,"set",e,"Interval"),ke(n,"set",e,"Immediate")}),t.__load_patch("requestAnimationFrame",n=>{ke(n,"request","cancel","AnimationFrame"),ke(n,"mozRequest","mozCancel","AnimationFrame"),ke(n,"webkitRequest","webkitCancel","AnimationFrame")}),t.__load_patch("blocking",(n,a)=>{let e=["alert","prompt","confirm"];for(let c=0;cfunction(w,_){return a.current.run(g,n,_,y)})}}),t.__load_patch("EventTarget",(n,a,e)=>{wt(n,e),Ct(n,e);let c=n.XMLHttpRequestEventTarget;c&&c.prototype&&e.patchEventTarget(n,e,[c.prototype])}),t.__load_patch("MutationObserver",(n,a,e)=>{ye("MutationObserver"),ye("WebKitMutationObserver")}),t.__load_patch("IntersectionObserver",(n,a,e)=>{ye("IntersectionObserver")}),t.__load_patch("FileReader",(n,a,e)=>{ye("FileReader")}),t.__load_patch("on_property",(n,a,e)=>{Dt(e,n)}),t.__load_patch("customElements",(n,a,e)=>{Rt(n,e)}),t.__load_patch("XHR",(n,a)=>{w(n);let e=j("xhrTask"),c=j("xhrSync"),f=j("xhrListener"),g=j("xhrScheduled"),T=j("xhrURL"),y=j("xhrErrorBeforeScheduled");function w(_){let P=_.XMLHttpRequest;if(!P)return;let L=P.prototype;function H(R){return R[e]}let z=L[Ne],$=L[Ze];if(!z){let R=_.XMLHttpRequestEventTarget;if(R){let b=R.prototype;z=b[Ne],$=b[Ze]}}let J="readystatechange",q="scheduled";function p(R){let b=R.data,D=b.target;D[g]=!1,D[y]=!1;let K=D[f];z||(z=D[Ne],$=D[Ze]),K&&$.call(D,J,K);let W=D[f]=()=>{if(D.readyState===D.DONE)if(!b.aborted&&D[g]&&R.state===q){let s=D[a.__symbol__("loadfalse")];if(D.status!==0&&s&&s.length>0){let i=R.invoke;R.invoke=function(){let r=D[a.__symbol__("loadfalse")];for(let E=0;Efunction(R,b){return R[c]=b[2]==!1,R[T]=b[1],V.apply(R,b)}),X="XMLHttpRequest.send",F=j("fetchTaskAborting"),k=j("fetchTaskScheduling"),U=ue(L,"send",()=>function(R,b){if(a.current[k]===!0||R[c])return U.apply(R,b);{let D={target:R,url:R[T],isPeriodic:!1,args:b,aborted:!1},K=xe(X,d,D,p,A);R&&R[y]===!0&&!D.aborted&&K.state===q&&K.invoke()}}),S=ue(L,"abort",()=>function(R,b){let D=H(R);if(D&&typeof D.type=="string"){if(D.cancelFn==null||D.data&&D.data.aborted)return;D.zone.cancelTask(D)}else if(a.current[F]===!0)return S.apply(R,b)})}}),t.__load_patch("geolocation",n=>{n.navigator&&n.navigator.geolocation&>(n.navigator.geolocation,["getCurrentPosition","watchPosition"])}),t.__load_patch("PromiseRejectionEvent",(n,a)=>{function e(c){return function(f){at(n,c).forEach(T=>{let y=n.PromiseRejectionEvent;if(y){let w=new y(c,{promise:f.promise,reason:f.rejection});T.invoke(w)}})}}n.PromiseRejectionEvent&&(a[j("unhandledPromiseRejectionHandler")]=e("unhandledrejection"),a[j("rejectionHandledHandler")]=e("rejectionhandled"))}),t.__load_patch("queueMicrotask",(n,a,e)=>{Pt(n,e)})}function Ot(t){t.__load_patch("ZoneAwarePromise",(n,a,e)=>{let c=Object.getOwnPropertyDescriptor,f=Object.defineProperty;function g(h){if(h&&h.toString===Object.prototype.toString){let l=h.constructor&&h.constructor.name;return(l||"")+": "+JSON.stringify(h)}return h?h.toString():Object.prototype.toString.call(h)}let T=e.symbol,y=[],w=n[T("DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION")]!==!1,_=T("Promise"),P=T("then"),L="__creationTrace__";e.onUnhandledError=h=>{if(e.showUncaughtError()){let l=h&&h.rejection;l?console.error("Unhandled Promise rejection:",l instanceof Error?l.message:l,"; Zone:",h.zone.name,"; Task:",h.task&&h.task.source,"; Value:",l,l instanceof Error?l.stack:void 0):console.error(h)}},e.microtaskDrainDone=()=>{for(;y.length;){let h=y.shift();try{h.zone.runGuarded(()=>{throw h.throwOriginal?h.rejection:h})}catch(l){z(l)}}};let H=T("unhandledPromiseRejectionHandler");function z(h){e.onUnhandledError(h);try{let l=a[H];typeof l=="function"&&l.call(this,h)}catch{}}function $(h){return h&&typeof h.then=="function"}function J(h){return h}function q(h){return M.reject(h)}let p=T("state"),d=T("value"),A=T("finally"),V=T("parentPromiseValue"),X=T("parentPromiseState"),F="Promise.then",k=null,U=!0,S=!1,R=0;function b(h,l){return o=>{try{I(h,l,o)}catch(u){I(h,!1,u)}}}let D=function(){let h=!1;return function(o){return function(){h||(h=!0,o.apply(null,arguments))}}},K="Promise resolved with itself",W=T("currentTaskTrace");function I(h,l,o){let u=D();if(h===o)throw new TypeError(K);if(h[p]===k){let v=null;try{(typeof o=="object"||typeof o=="function")&&(v=o&&o.then)}catch(C){return u(()=>{I(h,!1,C)})(),h}if(l!==S&&o instanceof M&&o.hasOwnProperty(p)&&o.hasOwnProperty(d)&&o[p]!==k)i(o),I(h,o[p],o[d]);else if(l!==S&&typeof v=="function")try{v.call(o,u(b(h,l)),u(b(h,!1)))}catch(C){u(()=>{I(h,!1,C)})()}else{h[p]=l;let C=h[d];if(h[d]=o,h[A]===A&&l===U&&(h[p]=h[X],h[d]=h[V]),l===S&&o instanceof Error){let m=a.currentTask&&a.currentTask.data&&a.currentTask.data[L];m&&f(o,W,{configurable:!0,enumerable:!1,writable:!0,value:m})}for(let m=0;m{try{let O=h[d],N=!!o&&A===o[A];N&&(o[V]=O,o[X]=C);let Z=l.run(m,void 0,N&&m!==q&&m!==J?[]:[O]);I(o,!0,Z)}catch(O){I(o,!1,O)}},o)}let E="function ZoneAwarePromise() { [native code] }",x=function(){},ee=n.AggregateError;class M{static toString(){return E}static resolve(l){return l instanceof M?l:I(new this(null),U,l)}static reject(l){return I(new this(null),S,l)}static withResolvers(){let l={};return l.promise=new M((o,u)=>{l.resolve=o,l.reject=u}),l}static any(l){if(!l||typeof l[Symbol.iterator]!="function")return Promise.reject(new ee([],"All promises were rejected"));let o=[],u=0;try{for(let m of l)u++,o.push(M.resolve(m))}catch{return Promise.reject(new ee([],"All promises were rejected"))}if(u===0)return Promise.reject(new ee([],"All promises were rejected"));let v=!1,C=[];return new M((m,O)=>{for(let N=0;N{v||(v=!0,m(Z))},Z=>{C.push(Z),u--,u===0&&(v=!0,O(new ee(C,"All promises were rejected")))})})}static race(l){let o,u,v=new this((O,N)=>{o=O,u=N});function C(O){o(O)}function m(O){u(O)}for(let O of l)$(O)||(O=this.resolve(O)),O.then(C,m);return v}static all(l){return M.allWithCallback(l)}static allSettled(l){return(this&&this.prototype instanceof M?this:M).allWithCallback(l,{thenCallback:u=>({status:"fulfilled",value:u}),errorCallback:u=>({status:"rejected",reason:u})})}static allWithCallback(l,o){let u,v,C=new this((Z,G)=>{u=Z,v=G}),m=2,O=0,N=[];for(let Z of l){$(Z)||(Z=this.resolve(Z));let G=O;try{Z.then(B=>{N[G]=o?o.thenCallback(B):B,m--,m===0&&u(N)},B=>{o?(N[G]=o.errorCallback(B),m--,m===0&&u(N)):v(B)})}catch(B){v(B)}m++,O++}return m-=2,m===0&&u(N),C}constructor(l){let o=this;if(!(o instanceof M))throw new Error("Must be an instanceof Promise.");o[p]=k,o[d]=[];try{let u=D();l&&l(u(b(o,U)),u(b(o,S)))}catch(u){I(o,!1,u)}}get[Symbol.toStringTag](){return"Promise"}get[Symbol.species](){return M}then(l,o){let u=this.constructor?.[Symbol.species];(!u||typeof u!="function")&&(u=this.constructor||M);let v=new u(x),C=a.current;return this[p]==k?this[d].push(C,v,l,o):r(this,C,v,l,o),v}catch(l){return this.then(null,l)}finally(l){let o=this.constructor?.[Symbol.species];(!o||typeof o!="function")&&(o=M);let u=new o(x);u[A]=A;let v=a.current;return this[p]==k?this[d].push(v,u,l,l):r(this,v,u,l,l),u}}M.resolve=M.resolve,M.reject=M.reject,M.race=M.race,M.all=M.all;let he=n[_]=n.Promise;n.Promise=M;let _e=T("thenPatched");function Q(h){let l=h.prototype,o=c(l,"then");if(o&&(o.writable===!1||!o.configurable))return;let u=l.then;l[P]=u,h.prototype.then=function(v,C){return new M((O,N)=>{u.call(this,O,N)}).then(v,C)},h[_e]=!0}e.patchThen=Q;function Te(h){return function(l,o){let u=h.apply(l,o);if(u instanceof M)return u;let v=u.constructor;return v[_e]||Q(v),u}}return he&&(Q(he),ue(n,"fetch",h=>Te(h))),Promise[a.__symbol__("uncaughtPromiseErrors")]=y,M})}function Nt(t){t.__load_patch("toString",n=>{let a=Function.prototype.toString,e=j("OriginalDelegate"),c=j("Promise"),f=j("Error"),g=function(){if(typeof this=="function"){let _=this[e];if(_)return typeof _=="function"?a.call(_):Object.prototype.toString.call(_);if(this===Promise){let P=n[c];if(P)return a.call(P)}if(this===Error){let P=n[f];if(P)return a.call(P)}}return a.call(this)};g[e]=a,Function.prototype.toString=g;let T=Object.prototype.toString,y="[object Promise]";Object.prototype.toString=function(){return typeof Promise=="function"&&this instanceof Promise?y:T.call(this)}})}function Zt(t,n,a,e,c){let f=Zone.__symbol__(e);if(n[f])return;let g=n[f]=n[e];n[e]=function(T,y,w){return y&&y.prototype&&c.forEach(function(_){let P=`${a}.${e}::`+_,L=y.prototype;try{if(L.hasOwnProperty(_)){let H=t.ObjectGetOwnPropertyDescriptor(L,_);H&&H.value?(H.value=t.wrapWithCurrentZone(H.value,P),t._redefineProperty(y.prototype,_,H)):L[_]&&(L[_]=t.wrapWithCurrentZone(L[_],P))}else L[_]&&(L[_]=t.wrapWithCurrentZone(L[_],P))}catch{}}),g.call(n,T,y,w)},t.attachOriginToPatched(n[e],g)}function Lt(t){t.__load_patch("util",(n,a,e)=>{let c=Ie(n);e.patchOnProperties=rt,e.patchMethod=ue,e.bindArguments=Fe,e.patchMacroTask=mt;let f=a.__symbol__("BLACK_LISTED_EVENTS"),g=a.__symbol__("UNPATCHED_EVENTS");n[g]&&(n[f]=n[g]),n[f]&&(a[f]=a[g]=n[f]),e.patchEventPrototype=bt,e.patchEventTarget=vt,e.isIEOrEdge=yt,e.ObjectDefineProperty=Me,e.ObjectGetOwnPropertyDescriptor=pe,e.ObjectCreate=_t,e.ArraySlice=Tt,e.patchClass=ye,e.wrapWithCurrentZone=Ve,e.filterProperties=lt,e.attachOriginToPatched=fe,e._redefineProperty=Object.defineProperty,e.patchCallbacks=Zt,e.getGlobalObjects=()=>({globalSources:ot,zoneSymbolEventNames:ne,eventNames:c,isBrowser:Ge,isMix:nt,isNode:De,TRUE_STR:ae,FALSE_STR:le,ZONE_SYMBOL_PREFIX:ve,ADD_EVENT_LISTENER_STR:je,REMOVE_EVENT_LISTENER_STR:He})})}function It(t){Ot(t),Nt(t),Lt(t)}var ut=dt();It(ut);St(ut); diff --git a/artemis/resources/showcase_ui/styles-CDJM3XQS.css b/artemis/resources/showcase_ui/styles-CDJM3XQS.css new file mode 100644 index 0000000..a0a5f15 --- /dev/null +++ b/artemis/resources/showcase_ui/styles-CDJM3XQS.css @@ -0,0 +1 @@ +@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/inter/v20/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Outfit;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJuktqQ4E.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Outfit;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJtEtq.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Outfit;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJuktqQ4E.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Outfit;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJtEtq.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Outfit;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJuktqQ4E.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Outfit;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJtEtq.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Outfit;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJuktqQ4E.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Outfit;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJtEtq.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Outfit;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJuktqQ4E.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Outfit;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/outfit/v15/QGYvz_MVcBeNP4NJtEtq.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format("woff2");unicode-range:U+0302-0303,U+0305,U+0307-0308,U+0310,U+0312,U+0315,U+031A,U+0326-0327,U+032C,U+032F-0330,U+0332-0333,U+0338,U+033A,U+0346,U+034D,U+0391-03A1,U+03A3-03A9,U+03B1-03C9,U+03D1,U+03D5-03D6,U+03F0-03F1,U+03F4-03F5,U+2016-2017,U+2034-2038,U+203C,U+2040,U+2043,U+2047,U+2050,U+2057,U+205F,U+2070-2071,U+2074-208E,U+2090-209C,U+20D0-20DC,U+20E1,U+20E5-20EF,U+2100-2112,U+2114-2115,U+2117-2121,U+2123-214F,U+2190,U+2192,U+2194-21AE,U+21B0-21E5,U+21F1-21F2,U+21F4-2211,U+2213-2214,U+2216-22FF,U+2308-230B,U+2310,U+2319,U+231C-2321,U+2336-237A,U+237C,U+2395,U+239B-23B7,U+23D0,U+23DC-23E1,U+2474-2475,U+25AF,U+25B3,U+25B7,U+25BD,U+25C1,U+25CA,U+25CC,U+25FB,U+266D-266F,U+27C0-27FF,U+2900-2AFF,U+2B0E-2B11,U+2B30-2B4C,U+2BFE,U+3030,U+FF5B,U+FF5D,U+1D400-1D7FF,U+1EE00-1EEFF}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format("woff2");unicode-range:U+0001-000C,U+000E-001F,U+007F-009F,U+20DD-20E0,U+20E2-20E4,U+2150-218F,U+2190,U+2192,U+2194-2199,U+21AF,U+21E6-21F0,U+21F3,U+2218-2219,U+2299,U+22C4-22C6,U+2300-243F,U+2440-244A,U+2460-24FF,U+25A0-27BF,U+2800-28FF,U+2921-2922,U+2981,U+29BF,U+29EB,U+2B00-2BFF,U+4DC0-4DFF,U+FFF9-FFFB,U+10140-1018E,U+10190-1019C,U+101A0,U+101D0-101FD,U+102E0-102FB,U+10E60-10E7E,U+1D2C0-1D2D3,U+1D2E0-1D37F,U+1F000-1F0FF,U+1F100-1F1AD,U+1F1E6-1F1FF,U+1F30D-1F30F,U+1F315,U+1F31C,U+1F31E,U+1F320-1F32C,U+1F336,U+1F378,U+1F37D,U+1F382,U+1F393-1F39F,U+1F3A7-1F3A8,U+1F3AC-1F3AF,U+1F3C2,U+1F3C4-1F3C6,U+1F3CA-1F3CE,U+1F3D4-1F3E0,U+1F3ED,U+1F3F1-1F3F3,U+1F3F5-1F3F7,U+1F408,U+1F415,U+1F41F,U+1F426,U+1F43F,U+1F441-1F442,U+1F444,U+1F446-1F449,U+1F44C-1F44E,U+1F453,U+1F46A,U+1F47D,U+1F4A3,U+1F4B0,U+1F4B3,U+1F4B9,U+1F4BB,U+1F4BF,U+1F4C8-1F4CB,U+1F4D6,U+1F4DA,U+1F4DF,U+1F4E3-1F4E6,U+1F4EA-1F4ED,U+1F4F7,U+1F4F9-1F4FB,U+1F4FD-1F4FE,U+1F503,U+1F507-1F50B,U+1F50D,U+1F512-1F513,U+1F53E-1F54A,U+1F54F-1F5FA,U+1F610,U+1F650-1F67F,U+1F687,U+1F68D,U+1F691,U+1F694,U+1F698,U+1F6AD,U+1F6B2,U+1F6B9-1F6BA,U+1F6BC,U+1F6C6-1F6CF,U+1F6D3-1F6D7,U+1F6E0-1F6EA,U+1F6F0-1F6F3,U+1F6F7-1F6FC,U+1F700-1F7FF,U+1F800-1F80B,U+1F810-1F847,U+1F850-1F859,U+1F860-1F887,U+1F890-1F8AD,U+1F8B0-1F8BB,U+1F8C0-1F8C1,U+1F900-1F90B,U+1F93B,U+1F946,U+1F984,U+1F996,U+1F9E9,U+1FA00-1FA6F,U+1FA70-1FA7C,U+1FA80-1FA89,U+1FA8F-1FAC6,U+1FACE-1FADC,U+1FADF-1FAE9,U+1FAF0-1FAF8,U+1FB00-1FBFF}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Roboto;font-style:normal;font-weight:400;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format("woff2");unicode-range:U+0302-0303,U+0305,U+0307-0308,U+0310,U+0312,U+0315,U+031A,U+0326-0327,U+032C,U+032F-0330,U+0332-0333,U+0338,U+033A,U+0346,U+034D,U+0391-03A1,U+03A3-03A9,U+03B1-03C9,U+03D1,U+03D5-03D6,U+03F0-03F1,U+03F4-03F5,U+2016-2017,U+2034-2038,U+203C,U+2040,U+2043,U+2047,U+2050,U+2057,U+205F,U+2070-2071,U+2074-208E,U+2090-209C,U+20D0-20DC,U+20E1,U+20E5-20EF,U+2100-2112,U+2114-2115,U+2117-2121,U+2123-214F,U+2190,U+2192,U+2194-21AE,U+21B0-21E5,U+21F1-21F2,U+21F4-2211,U+2213-2214,U+2216-22FF,U+2308-230B,U+2310,U+2319,U+231C-2321,U+2336-237A,U+237C,U+2395,U+239B-23B7,U+23D0,U+23DC-23E1,U+2474-2475,U+25AF,U+25B3,U+25B7,U+25BD,U+25C1,U+25CA,U+25CC,U+25FB,U+266D-266F,U+27C0-27FF,U+2900-2AFF,U+2B0E-2B11,U+2B30-2B4C,U+2BFE,U+3030,U+FF5B,U+FF5D,U+1D400-1D7FF,U+1EE00-1EEFF}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format("woff2");unicode-range:U+0001-000C,U+000E-001F,U+007F-009F,U+20DD-20E0,U+20E2-20E4,U+2150-218F,U+2190,U+2192,U+2194-2199,U+21AF,U+21E6-21F0,U+21F3,U+2218-2219,U+2299,U+22C4-22C6,U+2300-243F,U+2440-244A,U+2460-24FF,U+25A0-27BF,U+2800-28FF,U+2921-2922,U+2981,U+29BF,U+29EB,U+2B00-2BFF,U+4DC0-4DFF,U+FFF9-FFFB,U+10140-1018E,U+10190-1019C,U+101A0,U+101D0-101FD,U+102E0-102FB,U+10E60-10E7E,U+1D2C0-1D2D3,U+1D2E0-1D37F,U+1F000-1F0FF,U+1F100-1F1AD,U+1F1E6-1F1FF,U+1F30D-1F30F,U+1F315,U+1F31C,U+1F31E,U+1F320-1F32C,U+1F336,U+1F378,U+1F37D,U+1F382,U+1F393-1F39F,U+1F3A7-1F3A8,U+1F3AC-1F3AF,U+1F3C2,U+1F3C4-1F3C6,U+1F3CA-1F3CE,U+1F3D4-1F3E0,U+1F3ED,U+1F3F1-1F3F3,U+1F3F5-1F3F7,U+1F408,U+1F415,U+1F41F,U+1F426,U+1F43F,U+1F441-1F442,U+1F444,U+1F446-1F449,U+1F44C-1F44E,U+1F453,U+1F46A,U+1F47D,U+1F4A3,U+1F4B0,U+1F4B3,U+1F4B9,U+1F4BB,U+1F4BF,U+1F4C8-1F4CB,U+1F4D6,U+1F4DA,U+1F4DF,U+1F4E3-1F4E6,U+1F4EA-1F4ED,U+1F4F7,U+1F4F9-1F4FB,U+1F4FD-1F4FE,U+1F503,U+1F507-1F50B,U+1F50D,U+1F512-1F513,U+1F53E-1F54A,U+1F54F-1F5FA,U+1F610,U+1F650-1F67F,U+1F687,U+1F68D,U+1F691,U+1F694,U+1F698,U+1F6AD,U+1F6B2,U+1F6B9-1F6BA,U+1F6BC,U+1F6C6-1F6CF,U+1F6D3-1F6D7,U+1F6E0-1F6EA,U+1F6F0-1F6F3,U+1F6F7-1F6FC,U+1F700-1F7FF,U+1F800-1F80B,U+1F810-1F847,U+1F850-1F859,U+1F860-1F887,U+1F890-1F8AD,U+1F8B0-1F8BB,U+1F8C0-1F8C1,U+1F900-1F90B,U+1F93B,U+1F946,U+1F984,U+1F996,U+1F9E9,U+1FA00-1FA6F,U+1FA70-1FA7C,U+1FA80-1FA89,U+1FA8F-1FAC6,U+1FACE-1FADC,U+1FADF-1FAE9,U+1FAF0-1FAF8,U+1FB00-1FBFF}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Roboto;font-style:normal;font-weight:500;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3GUBGEe.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3iUBGEe.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3CUBGEe.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3-UBGEe.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMawCUBGEe.woff2) format("woff2");unicode-range:U+0302-0303,U+0305,U+0307-0308,U+0310,U+0312,U+0315,U+031A,U+0326-0327,U+032C,U+032F-0330,U+0332-0333,U+0338,U+033A,U+0346,U+034D,U+0391-03A1,U+03A3-03A9,U+03B1-03C9,U+03D1,U+03D5-03D6,U+03F0-03F1,U+03F4-03F5,U+2016-2017,U+2034-2038,U+203C,U+2040,U+2043,U+2047,U+2050,U+2057,U+205F,U+2070-2071,U+2074-208E,U+2090-209C,U+20D0-20DC,U+20E1,U+20E5-20EF,U+2100-2112,U+2114-2115,U+2117-2121,U+2123-214F,U+2190,U+2192,U+2194-21AE,U+21B0-21E5,U+21F1-21F2,U+21F4-2211,U+2213-2214,U+2216-22FF,U+2308-230B,U+2310,U+2319,U+231C-2321,U+2336-237A,U+237C,U+2395,U+239B-23B7,U+23D0,U+23DC-23E1,U+2474-2475,U+25AF,U+25B3,U+25B7,U+25BD,U+25C1,U+25CA,U+25CC,U+25FB,U+266D-266F,U+27C0-27FF,U+2900-2AFF,U+2B0E-2B11,U+2B30-2B4C,U+2BFE,U+3030,U+FF5B,U+FF5D,U+1D400-1D7FF,U+1EE00-1EEFF}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMaxKUBGEe.woff2) format("woff2");unicode-range:U+0001-000C,U+000E-001F,U+007F-009F,U+20DD-20E0,U+20E2-20E4,U+2150-218F,U+2190,U+2192,U+2194-2199,U+21AF,U+21E6-21F0,U+21F3,U+2218-2219,U+2299,U+22C4-22C6,U+2300-243F,U+2440-244A,U+2460-24FF,U+25A0-27BF,U+2800-28FF,U+2921-2922,U+2981,U+29BF,U+29EB,U+2B00-2BFF,U+4DC0-4DFF,U+FFF9-FFFB,U+10140-1018E,U+10190-1019C,U+101A0,U+101D0-101FD,U+102E0-102FB,U+10E60-10E7E,U+1D2C0-1D2D3,U+1D2E0-1D37F,U+1F000-1F0FF,U+1F100-1F1AD,U+1F1E6-1F1FF,U+1F30D-1F30F,U+1F315,U+1F31C,U+1F31E,U+1F320-1F32C,U+1F336,U+1F378,U+1F37D,U+1F382,U+1F393-1F39F,U+1F3A7-1F3A8,U+1F3AC-1F3AF,U+1F3C2,U+1F3C4-1F3C6,U+1F3CA-1F3CE,U+1F3D4-1F3E0,U+1F3ED,U+1F3F1-1F3F3,U+1F3F5-1F3F7,U+1F408,U+1F415,U+1F41F,U+1F426,U+1F43F,U+1F441-1F442,U+1F444,U+1F446-1F449,U+1F44C-1F44E,U+1F453,U+1F46A,U+1F47D,U+1F4A3,U+1F4B0,U+1F4B3,U+1F4B9,U+1F4BB,U+1F4BF,U+1F4C8-1F4CB,U+1F4D6,U+1F4DA,U+1F4DF,U+1F4E3-1F4E6,U+1F4EA-1F4ED,U+1F4F7,U+1F4F9-1F4FB,U+1F4FD-1F4FE,U+1F503,U+1F507-1F50B,U+1F50D,U+1F512-1F513,U+1F53E-1F54A,U+1F54F-1F5FA,U+1F610,U+1F650-1F67F,U+1F687,U+1F68D,U+1F691,U+1F694,U+1F698,U+1F6AD,U+1F6B2,U+1F6B9-1F6BA,U+1F6BC,U+1F6C6-1F6CF,U+1F6D3-1F6D7,U+1F6E0-1F6EA,U+1F6F0-1F6F3,U+1F6F7-1F6FC,U+1F700-1F7FF,U+1F800-1F80B,U+1F810-1F847,U+1F850-1F859,U+1F860-1F887,U+1F890-1F8AD,U+1F8B0-1F8BB,U+1F8C0-1F8C1,U+1F900-1F90B,U+1F93B,U+1F946,U+1F984,U+1F996,U+1F9E9,U+1FA00-1FA6F,U+1FA70-1FA7C,U+1FA80-1FA89,U+1FA8F-1FAC6,U+1FACE-1FADC,U+1FADF-1FAE9,U+1FAF0-1FAF8,U+1FB00-1FBFF}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3OUBGEe.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3KUBGEe.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Roboto;font-style:normal;font-weight:700;font-stretch:100%;font-display:swap;src:url(https://fonts.gstatic.com/s/roboto/v51/KFO7CnqEu92Fr1ME7kSn66aGLdTylUAMa3yUBA.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Material Symbols Outlined;font-style:normal;font-weight:100 700;src:url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v368/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsI.woff2) format("woff2")}.material-symbols-outlined{font-family:Material Symbols Outlined;font-weight:400;font-style:normal;font-size:24px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-feature-settings:"liga";-webkit-font-smoothing:antialiased}*,*:before,*:after{box-sizing:border-box;font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}.material-symbols-outlined{font-family:Material Symbols Outlined!important;font-weight:400;font-style:normal;font-size:20px;line-height:1;letter-spacing:normal;text-transform:none;display:inline-block;white-space:nowrap;word-wrap:normal;direction:ltr;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;font-feature-settings:"liga";font-variation-settings:"FILL" 0,"wght" 300,"GRAD" 0,"opsz" 20}body,input,button,select,textarea{font-family:Google Sans,Google Sans Text,Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif}body{margin:0;padding:0;color:#1e293b;background-color:#f8f9fa;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} diff --git a/artemis/runtime/__init__.py b/artemis/runtime/__init__.py index 8199fcb..1ec8e98 100644 --- a/artemis/runtime/__init__.py +++ b/artemis/runtime/__init__.py @@ -56,6 +56,12 @@ from artemis.runtime.server_lifecycle import ( write_server_info, ) from artemis.runtime.supervisor import ProcessSupervisor, process_supervisor +from artemis.runtime.cancel_requests import ( + clear_cancel_request, + is_cancel_requested, + request_cancel, + watch_for_cancel_request, +) __all__ = [ "ADB_ENDPOINT_ID_ENV", @@ -68,6 +74,7 @@ __all__ = [ "DevicePool", "DeviceStatus", "InvalidAdbEndpoint", + "clear_cancel_request", "clear_server_info", "current_adb_endpoint", "device_pool", @@ -78,11 +85,13 @@ __all__ = [ "get_daemon_status", "get_server_status", "is_daemon_running", + "is_cancel_requested", "is_port_in_use", "pid_is_alive", "ProcessSupervisor", "process_supervisor", "read_server_info", + "request_cancel", "sanitize_device_state", "screen_awake_service", "shutdown_awake_service", @@ -92,5 +101,6 @@ __all__ = [ "submit_batch_to_daemon", "submit_task_to_daemon", "wait_for_daemon_task", + "watch_for_cancel_request", "write_server_info", ] diff --git a/artemis/runtime/awake_lease.py b/artemis/runtime/awake_lease.py index 371804c..75cd625 100644 --- a/artemis/runtime/awake_lease.py +++ b/artemis/runtime/awake_lease.py @@ -158,8 +158,7 @@ class ScreenAwakeLease: if result.returncode != 0: detail = (result.stderr or result.stdout).strip() logger.warning( - f"Could not {description} on {self.device_id}: " - f"{detail or 'ADB command failed'}" + f"Could not {description} on {self.device_id}: {detail or 'ADB command failed'}" ) return result except Exception as exc: @@ -279,7 +278,5 @@ class ScreenAwakeLease: return False return self._drain_references() except Exception as exc: - logger.warning( - f"Could not clean unowned screen wake locks on {self.device_id}: {exc}" - ) + logger.warning(f"Could not clean unowned screen wake locks on {self.device_id}: {exc}") return False diff --git a/artemis/runtime/awake_service.py b/artemis/runtime/awake_service.py index 7c9c864..12f4e37 100644 --- a/artemis/runtime/awake_service.py +++ b/artemis/runtime/awake_service.py @@ -207,13 +207,22 @@ class ScreenAwakeService: break if device_id: results[device_id] = self.ensure_device(device_id) - if _awake_enabled() and os.environ.get("ARTEMIS_CLOUD_MODE") != "1" and not self._shutdown_requested: + if ( + _awake_enabled() + and os.environ.get("ARTEMIS_CLOUD_MODE") != "1" + and not self._shutdown_requested + ): self._start_device_monitor() return results def ensure_device(self, device_id: str) -> str | None: """Configure one device once per process; Android owns unplug behavior.""" - if not device_id or not _awake_enabled() or os.environ.get("ARTEMIS_CLOUD_MODE") == "1" or self._shutdown_requested: + if ( + not device_id + or not _awake_enabled() + or os.environ.get("ARTEMIS_CLOUD_MODE") == "1" + or self._shutdown_requested + ): return None with self._lock: existing = self._strategies.get(device_id) diff --git a/artemis/runtime/cancel_requests.py b/artemis/runtime/cancel_requests.py new file mode 100644 index 0000000..5b4fbeb --- /dev/null +++ b/artemis/runtime/cancel_requests.py @@ -0,0 +1,254 @@ +# Copyright 2026 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Cross-process cooperative cancellation for task workers. + +Workers use ``CREATE_NO_WINDOW`` on Windows, so console signals cannot reach +them reliably. The daemon writes a cancel marker in the shared temp directory; +the worker polls it and cancels its asyncio task, allowing recording, trace +and device-lease cleanup to finish. The daemon kills unresponsive workers +after a deadline. + +Markers use the session id, falling back to the PID and process creation time +to avoid cancelling a different process after PID reuse. +""" + +from __future__ import annotations + +from collections.abc import Callable +import json +import os +from pathlib import Path +import time +from typing import Any +import uuid + +from artemis.config.paths import get_temp_dir + +CANCEL_DIR_NAME = "cancel-requests" +MARKER_TTL_SECONDS = 3600.0 +DEFAULT_POLL_SECONDS = 0.5 + + +def cancel_request_dir() -> Path: + """Directory holding cancel markers (created on demand).""" + directory = get_temp_dir(CANCEL_DIR_NAME) + directory.mkdir(parents=True, exist_ok=True) + return directory + + +def _session_marker(session_id: str) -> Path: + safe = "".join(ch if ch.isalnum() or ch in "-_." else "_" for ch in str(session_id).strip()) + return cancel_request_dir() / f"session-{safe}.cancel" + + +def _pid_marker(pid: int) -> Path: + return cancel_request_dir() / f"pid-{int(pid)}.cancel" + + +def _write_marker(path: Path, payload: dict[str, Any]) -> None: + tmp = path.with_name(f"{path.name}.{uuid.uuid4().hex}.tmp") + tmp.write_text(json.dumps(payload), encoding="utf-8") + os.replace(tmp, path) + + +def _read_marker(path: Path) -> dict[str, Any] | None: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except (OSError, ValueError): + return None + return payload if isinstance(payload, dict) else None + + +def _is_expired(payload: dict[str, Any] | None, path: Path, now: float) -> bool: + requested_at = None + if payload is not None: + try: + requested_at = float(payload.get("requested_at")) + except (TypeError, ValueError): + requested_at = None + if requested_at is None: + try: + requested_at = path.stat().st_mtime + except OSError: + return True + return (now - requested_at) > MARKER_TTL_SECONDS + + +def request_cancel( + *, + session_id: str | None = None, + pid: int | None = None, + process_created_at: float = 0.0, + reason: str = "", +) -> list[Path]: + """Ask the worker identified by ``session_id`` and/or ``pid`` to cancel. + + Returns the marker paths that were written. Writing is best-effort: an + unwritable temp directory yields an empty list and the caller falls back + to a hard kill. + """ + written: list[Path] = [] + now = time.time() + payload = { + "requested_at": now, + "reason": reason, + "session_id": str(session_id) if session_id else None, + "pid": int(pid) if pid else None, + "process_created_at": float(process_created_at or 0.0), + } + targets: list[Path] = [] + if session_id: + targets.append(_session_marker(str(session_id))) + if pid: + targets.append(_pid_marker(int(pid))) + for target in targets: + try: + _write_marker(target, payload) + written.append(target) + except OSError: + continue + purge_expired(now) + return written + + +def is_cancel_requested( + *, + session_id: str | None = None, + pid: int | None = None, + process_created_at: float | None = None, +) -> bool: + """Return True when a live cancel marker targets this worker. + + A PID marker is honoured only when its recorded process creation time is + absent or matches ``process_created_at`` (within one second), which + protects a fresh worker from a marker left behind for a dead one. + """ + now = time.time() + if session_id: + path = _session_marker(str(session_id)) + if path.exists(): + payload = _read_marker(path) + if not _is_expired(payload, path, now): + return True + if pid: + path = _pid_marker(int(pid)) + if path.exists(): + payload = _read_marker(path) + if payload is None: + # Partially written or unreadable marker: retry on the next poll. + return False + if _is_expired(payload, path, now): + return False + try: + recorded = float(payload.get("process_created_at") or 0.0) + except (TypeError, ValueError): + recorded = 0.0 + if ( + recorded > 0 + and process_created_at is not None + and process_created_at > 0 + and abs(recorded - process_created_at) >= 1.0 + ): + return False + return True + return False + + +def clear_cancel_request(*, session_id: str | None = None, pid: int | None = None) -> None: + """Remove the markers for a worker (best-effort).""" + targets: list[Path] = [] + if session_id: + targets.append(_session_marker(str(session_id))) + if pid: + targets.append(_pid_marker(int(pid))) + for target in targets: + try: + target.unlink(missing_ok=True) + except OSError: + continue + + +def purge_expired(now: float | None = None) -> int: + """Delete markers older than the TTL. Returns the number removed.""" + now = time.time() if now is None else now + removed = 0 + try: + entries = list(cancel_request_dir().glob("*.cancel")) + except OSError: + return 0 + for path in entries: + payload = _read_marker(path) + if _is_expired(payload, path, now): + try: + path.unlink(missing_ok=True) + removed += 1 + except OSError: + continue + return removed + + +def current_process_created_at() -> float: + """Creation time of the current process, or 0.0 when unavailable.""" + try: + import psutil + + return float(psutil.Process(os.getpid()).create_time()) + except Exception: + return 0.0 + + +async def watch_for_cancel_request( + on_cancel: Callable[[], Any], + *, + session_id: str | None = None, + pid: int | None = None, + process_created_at: float | None = None, + poll_seconds: float | None = None, +) -> bool: + """Poll for a cancel marker and invoke ``on_cancel`` once it appears. + + Returns True when a cancellation was delivered. The coroutine runs until + it delivers one or is itself cancelled, so callers should run it as a + background task alongside the work it guards. + """ + import asyncio + + if poll_seconds is None: + try: + poll_seconds = float(os.getenv("ARTEMIS_CANCEL_POLL_SECONDS", DEFAULT_POLL_SECONDS)) + except ValueError: + poll_seconds = DEFAULT_POLL_SECONDS + poll_seconds = max(0.05, poll_seconds) + if pid is None: + pid = os.getpid() + if process_created_at is None: + process_created_at = current_process_created_at() + + while True: + try: + requested = is_cancel_requested( + session_id=session_id, + pid=pid, + process_created_at=process_created_at, + ) + except Exception: + requested = False + if requested: + clear_cancel_request(session_id=session_id, pid=pid) + result = on_cancel() + if asyncio.iscoroutine(result): + await result + return True + await asyncio.sleep(poll_seconds) diff --git a/artemis/runtime/daemon_client.py b/artemis/runtime/daemon_client.py index f300221..196a19c 100644 --- a/artemis/runtime/daemon_client.py +++ b/artemis/runtime/daemon_client.py @@ -123,7 +123,9 @@ def spawn_daemon( try: proc = subprocess.Popen(cmd, **kwargs) - logger.info(f"Spawned Artemis Daemon in background (PID {proc.pid}) at http://{host}:{port}") + logger.info( + f"Spawned Artemis Daemon in background (PID {proc.pid}) at http://{host}:{port}" + ) return proc except Exception as exc: logger.warning(f"Could not auto-spawn Artemis Daemon: {exc}") @@ -191,11 +193,17 @@ def submit_task_to_daemon( session_id: str | None = None, ingress: str = "client", conversation_id: str | None = None, + verification_level: str | None = None, + explorer_mode: str | None = None, base_url: str | None = None, timeout: float = 15.0, ) -> dict[str, Any] | None: """Submit a task to the running Daemon via REST API. + ``verification_level`` ('off' | 'final' | 'checkpoints' | 'strict') and + ``explorer_mode`` ('flash' | 'pro' | 'ultra') are the Pro-profile tuning + knobs of ``/api/run``; they are forwarded verbatim and ignored by Flash. + Returns the response JSON dict if successfully enqueued, or None on error. """ url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/run" @@ -205,6 +213,8 @@ def submit_task_to_daemon( "device_serial": device_serial, "expected_output": expected_output, "enable_outputter": enable_outputter, + "verification_level": verification_level, + "explorer_mode": explorer_mode, "locked_app_package": locked_app_package, "app_path": app_path, "session_id": session_id, @@ -307,16 +317,24 @@ def submit_batch_to_daemon( profile: str = "flash", device_serial: str | None = None, ingress: str = "cli", + verification_level: str | None = None, + explorer_mode: str | None = None, base_url: str | None = None, timeout: float = 15.0, ) -> dict[str, Any] | None: - """Submit a batch of goals to the running Daemon.""" + """Submit a batch of goals to the running Daemon. + + ``verification_level`` / ``explorer_mode`` apply to every goal of the batch + (see :func:`submit_task_to_daemon`). + """ url = f"{base_url or f'http://{DEFAULT_DAEMON_HOST}:{DEFAULT_DAEMON_PORT}'}/api/run" payload = { "goals": goals, "profile": profile, "device_serial": device_serial, "ingress": ingress, + "verification_level": verification_level, + "explorer_mode": explorer_mode, } try: data = json.dumps(payload).encode("utf-8") @@ -376,4 +394,3 @@ def wait_for_daemon_task( time.sleep(poll_interval) return {"session_id": session_id, "status": "timeout", "error": f"Timed out after {timeout}s"} - diff --git a/artemis/runtime/device_lock.py b/artemis/runtime/device_lock.py index 38429a0..fcc96f0 100644 --- a/artemis/runtime/device_lock.py +++ b/artemis/runtime/device_lock.py @@ -62,17 +62,9 @@ class DeviceLockOwner: device_id=str(value["device_id"]), description=str(value.get("description", "unknown task")), acquired_at=str(value.get("acquired_at", "unknown time")), - session_id=( - str(value["session_id"]) - if value.get("session_id") is not None - else None - ), + session_id=(str(value["session_id"]) if value.get("session_id") is not None else None), ingress=(str(value["ingress"]) if value.get("ingress") is not None else None), - lock_scope=( - str(value["lock_scope"]) - if value.get("lock_scope") is not None - else None - ), + lock_scope=(str(value["lock_scope"]) if value.get("lock_scope") is not None else None), ) @@ -142,8 +134,8 @@ class DeviceExecutionLock: self.queue_dir = lock_dir / "artemis-global-device.queue" self._initial_queue_ticket = queue_ticket or os.environ.pop(self.QUEUE_TICKET_ENV, None) self.queue_ticket = self._initial_queue_ticket - self.session_id = session_id or os.getenv("ARTEMIS_SESSION_ID") or os.getenv( - "ARTEMIS_CLOUD_SESSION_ID" + self.session_id = ( + session_id or os.getenv("ARTEMIS_SESSION_ID") or os.getenv("ARTEMIS_CLOUD_SESSION_ID") ) self.ingress = ingress or os.getenv("ARTEMIS_TASK_INGRESS") or "sdk" self._queue_path: Path | None = None @@ -342,9 +334,7 @@ class DeviceExecutionLock: entries.append((wait_path, None)) continue same_scope = ( - not owner.lock_scope - or not target_scope - or owner.lock_scope == target_scope + not owner.lock_scope or not target_scope or owner.lock_scope == target_scope ) if not same_scope: continue @@ -645,10 +635,16 @@ class DeviceExecutionLock: # In strict serial mode, global FIFO applies; re-entrant parent processes are always eligible active_owners = self.get_active_owners() is_reentrant = any( - o.pid == os.getpid() and ((self.session_id and o.session_id == self.session_id) or o.token == self.token) + o.pid == os.getpid() + and ( + (self.session_id and o.session_id == self.session_id) + or o.token == self.token + ) for o in active_owners.values() ) - is_eligible = is_reentrant or bool(all_wait_files and all_wait_files[0] == self._queue_path) + is_eligible = is_reentrant or bool( + all_wait_files and all_wait_files[0] == self._queue_path + ) else: # In multi-device parallel mode, per-device FIFO applies. # Unclaimed pending/any tickets are parked on at most one @@ -713,7 +709,10 @@ class DeviceExecutionLock: time.sleep(0.01) if owner is None: try: - if path.exists() and time.time() - path.stat().st_mtime >= cls._MALFORMED_LOCK_GRACE_SECONDS: + if ( + path.exists() + and time.time() - path.stat().st_mtime >= cls._MALFORMED_LOCK_GRACE_SECONDS + ): cls._safe_unlink(path) except OSError: pass @@ -803,9 +802,7 @@ class DeviceExecutionLock: return [] target_scope = os.getenv(cls.LOCK_SCOPE_ENV) or None - clean_target = ( - cls._normalize_lock_id(device_id, target_scope) if device_id else None - ) + clean_target = cls._normalize_lock_id(device_id, target_scope) if device_id else None active_tokens = {o.token for o in cls.get_active_owners().values()} queued: list[dict[str, Any]] = [] @@ -833,19 +830,21 @@ class DeviceExecutionLock: except OSError: created_at = time.time() - queued.append({ - "session_id": owner.session_id or f"queued-{owner.token[:8]}", - "goal": owner.description, - "device_id": owner.device_id, - "device_serial": owner.device_id, - "adb_endpoint_id": owner.lock_scope, - "pid": owner.pid, - "token": owner.token, - "ingress": owner.ingress or "unknown", - "status": "pending", - "created_at": created_at, - "start_time": created_at, - }) + queued.append( + { + "session_id": owner.session_id or f"queued-{owner.token[:8]}", + "goal": owner.description, + "device_id": owner.device_id, + "device_serial": owner.device_id, + "adb_endpoint_id": owner.lock_scope, + "pid": owner.pid, + "token": owner.token, + "ingress": owner.ingress or "unknown", + "status": "pending", + "created_at": created_at, + "start_time": created_at, + } + ) return queued @classmethod @@ -859,12 +858,13 @@ class DeviceExecutionLock: device_id, os.getenv(cls.LOCK_SCOPE_ENV) or None, ) - return ( - lock_dir / f"artemis-device-{clean_id}.lock" - ).exists() or (lock_dir / "artemis-global-device.lock").exists() - return bool(list(lock_dir.glob("artemis-device-*.lock"))) or ( - lock_dir / "artemis-global-device.lock" - ).exists() + return (lock_dir / f"artemis-device-{clean_id}.lock").exists() or ( + lock_dir / "artemis-global-device.lock" + ).exists() + return ( + bool(list(lock_dir.glob("artemis-device-*.lock"))) + or (lock_dir / "artemis-global-device.lock").exists() + ) @classmethod def is_active_owner(cls, expected: DeviceLockOwner, device_id: str | None = None) -> bool: @@ -968,10 +968,7 @@ class DeviceExecutionLock: continue if owner is None: try: - if ( - time.time() - path.stat().st_mtime - < cls._MALFORMED_LOCK_GRACE_SECONDS - ): + if time.time() - path.stat().st_mtime < cls._MALFORMED_LOCK_GRACE_SECONDS: continue except OSError: continue diff --git a/artemis/runtime/device_pool.py b/artemis/runtime/device_pool.py index c186ce1..794f6cd 100644 --- a/artemis/runtime/device_pool.py +++ b/artemis/runtime/device_pool.py @@ -231,7 +231,9 @@ class DevicePool: if self._async_inflight is not None and self._async_inflight[1] is task: self._async_inflight = None - async def _enumerate_async_uncached(self) -> list[tuple[str, str, str | None, str | None]] | None: + async def _enumerate_async_uncached( + self, + ) -> list[tuple[str, str, str | None, str | None]] | None: raw = await self._query_adb_devices_async() if raw is None: return self._cached_snapshot(allow_stale=True) @@ -300,7 +302,9 @@ class DevicePool: devices: list[DeviceStatus] = [] for serial, state, model, product in raw_devices: - is_emu = serial.startswith("emulator-") or "127.0.0.1" in serial or "localhost" in serial + is_emu = ( + serial.startswith("emulator-") or "127.0.0.1" in serial or "localhost" in serial + ) clean_id = DeviceExecutionLock._normalize_lock_id( serial, os.getenv(DeviceExecutionLock.LOCK_SCOPE_ENV) or None, @@ -388,9 +392,7 @@ class DevicePool: async def validate_explicit_serial_async(self, requested_serial: str) -> str | None: """Async variant of validate_explicit_serial.""" - return self._explicit_serial_error( - requested_serial, await self.try_list_devices_async() - ) + return self._explicit_serial_error(requested_serial, await self.try_list_devices_async()) def get_ready_devices(self) -> list[DeviceStatus]: """Return devices in authorized 'device' state.""" diff --git a/artemis/runtime/server_lifecycle.py b/artemis/runtime/server_lifecycle.py index c8ce575..b0932ac 100644 --- a/artemis/runtime/server_lifecycle.py +++ b/artemis/runtime/server_lifecycle.py @@ -222,7 +222,7 @@ def find_server_pids(port: int = 8000) -> list[int]: token = token.strip() if token.isdigit(): discovered.add(int(token)) - except Exception: + except (OSError, ValueError, subprocess.SubprocessError): pass # 2b. fuser (Linux fallback) @@ -239,7 +239,7 @@ def find_server_pids(port: int = 8000) -> list[int]: token = token.strip() if token.isdigit(): discovered.add(int(token)) - except Exception: + except (OSError, ValueError, subprocess.SubprocessError): pass # 2c. netstat (Windows fallback) @@ -258,7 +258,7 @@ def find_server_pids(port: int = 8000) -> list[int]: parts = line.strip().split() if parts and parts[-1].isdigit(): discovered.add(int(parts[-1])) - except Exception: + except (OSError, ValueError, subprocess.SubprocessError): pass # 2d. psutil net_connections fallback @@ -270,8 +270,9 @@ def find_server_pids(port: int = 8000) -> list[int]: if conn.laddr and conn.laddr.port == port: if conn.pid: discovered.add(conn.pid) - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + # psutil is optional and net_connections() fails in platform-specific ways. + logger.debug(f"psutil net_connections port scan skipped: {exc}", exc_info=True) # 3. Process table scan if still nothing found on port but port is in use if not discovered and is_port_in_use(port): @@ -288,8 +289,9 @@ def find_server_pids(port: int = 8000) -> list[int]: discovered.add(proc.info["pid"]) except (psutil.NoSuchProcess, psutil.AccessDenied): continue - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + # psutil is optional and process_iter() fails in platform-specific ways. + logger.debug(f"psutil process table scan skipped: {exc}", exc_info=True) return sorted(discovered) @@ -406,8 +408,9 @@ def stop_server( if pid != current_pid: try: ProcessSupervisor.terminate_tree(pid, timeout_seconds=0.5) - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + # Force-kill is best effort; the port check below reports the outcome. + logger.debug(f"Force kill of pid {pid} failed: {exc}", exc_info=True) time.sleep(0.3) # Cleanup stale device execution locks left by terminated server diff --git a/artemis/runtime/trace_store.py b/artemis/runtime/trace_store.py index d3d41cc..256fbab 100644 --- a/artemis/runtime/trace_store.py +++ b/artemis/runtime/trace_store.py @@ -270,9 +270,7 @@ def read_status(trace_id: str) -> dict[str, Any] | None: try: _replace_with_retry(path, f"{path}.corrupt") except OSError as move_exc: - logger.warning( - f"Could not quarantine corrupt status file {path}: {move_exc}" - ) + logger.warning(f"Could not quarantine corrupt status file {path}: {move_exc}") return None except OSError as exc: logger.warning(f"Could not read status.json for trace {trace_id} at {path}: {exc}") diff --git a/artemis/sdk/agent.py b/artemis/sdk/agent.py index b999718..14ec2d4 100644 --- a/artemis/sdk/agent.py +++ b/artemis/sdk/agent.py @@ -55,6 +55,7 @@ from artemis.controllers.platform_specific_commands_controller import ( get_first_device, ) from artemis.runtime import DeviceExecutionLock +from artemis.runtime.cancel_requests import watch_for_cancel_request from artemis.data_engine.engine import DataEngine from artemis.data_engine.trace import DataEngineCallbackHandler from artemis.graph.graph import get_graph @@ -264,11 +265,11 @@ class Agent: client = genai.Client(api_key=key) # 2. Pre-warm LangChain client - chat = ChatGoogleGenerativeAI(model="gemini-3.7-flash", google_api_key=key) + chat = ChatGoogleGenerativeAI(model="gemini-3.8-flash", google_api_key=key) # Fire both calls concurrently in the background await asyncio.gather( - client.aio.models.count_tokens(model="gemini-3.7-flash", contents="ping"), + client.aio.models.count_tokens(model="gemini-3.8-flash", contents="ping"), chat.ainvoke("ping"), return_exceptions=True, ) @@ -588,8 +589,12 @@ class Agent: queue_cancel_event.set() try: await asyncio.shield(acquire_task) - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + # Draining the lock acquisition after cancellation is best effort. + logger.debug( + f"Device lock acquisition drain after cancel failed: {exc}", + exc_info=True, + ) raise # All device mutation and UI initialization happens only after # this task reaches the head of the one global FIFO queue. @@ -652,8 +657,11 @@ class Agent: message="Invoking FlashRunner...", ) + # An explicit max_steps caps the reactive loop; the + # default leaves the cap to agent.flash.max_turns + # (0 = unlimited). max_turns = ( - request.max_steps if request.max_steps != RECURSION_LIMIT else 35 + request.max_steps if request.max_steps != RECURSION_LIMIT else None ) runner = FlashRunner(context, goal=request.goal, max_turns=max_turns) flash_result = await runner.run(state) @@ -833,6 +841,14 @@ class Agent: raise finally: + try: + # Background ADB processes (logcat, screenrecord, ...) started + # by the Operator must not outlive the automation task. + from artemis.tools.command_tool import shutdown_adb_background_tasks + + await asyncio.wait_for(shutdown_adb_background_tasks(context), timeout=15.0) + except Exception as e: + logger.warning(f"[{task_name}] Failed to stop background ADB tasks: {e}") try: await self._finalize_tracing_safely(task=task, context=context) finally: @@ -856,11 +872,42 @@ class Agent: except asyncio.CancelledError: pass + cancel_watcher: asyncio.Task | None = None try: self._current_task = asyncio.create_task(_execute_task_logic()) + cancel_watcher = asyncio.create_task(self._watch_external_cancel(task_name)) return await self._current_task finally: self._current_task = None + if cancel_watcher is not None and not cancel_watcher.done(): + cancel_watcher.cancel() + try: + await cancel_watcher + except asyncio.CancelledError: + pass + + async def _watch_external_cancel(self, task_name: str) -> None: + """Cancel the running task when another process drops a cancel marker. + + The admin console (and other ingresses) cannot deliver a signal to a + worker portably, so they write a marker keyed by session id / pid. + Reacting here routes the request through the same cancellation path + as Ctrl+C: the recording is stopped and remuxed, the trace folder is + compiled and renamed, and the device lease is released. + """ + + def _on_cancel() -> None: + logger.warning( + f"[{task_name}] External cancel request received; stopping the task gracefully." + ) + self.stop_current_task() + + try: + await watch_for_cancel_request(_on_cancel, session_id=self._session_id) + except asyncio.CancelledError: + raise + except Exception as exc: + logger.debug(f"[{task_name}] Cancel watcher stopped: {exc}") def stop_current_task(self): """Requests cancellation of the currently running automation task.""" @@ -1049,7 +1096,6 @@ class Agent: assert_failure_policy=self._config.assert_failure_policy, disable_device_probes=self._config.disable_device_probes, disable_planner_validation=self._config.disable_planner_validation, - planner_validation_threshold=self._config.planner_validation_threshold, enable_committee=self._config.enable_committee, committee_debate_rounds=self._config.committee_debate_rounds, disable_outputter=self._config.disable_outputter, diff --git a/artemis/sdk/builders/agent_config_builder.py b/artemis/sdk/builders/agent_config_builder.py index 2bcd3f8..4786005 100644 --- a/artemis/sdk/builders/agent_config_builder.py +++ b/artemis/sdk/builders/agent_config_builder.py @@ -21,6 +21,7 @@ from typing import Any from langchain_core.callbacks.base import Callbacks from artemis.config import ( ExplorerVersion, + checker_overrides_for_level, get_default_llm_config, load_agent_config, settings, @@ -83,7 +84,6 @@ class AgentConfigBuilder: else: self._video_recording_tools_enabled = detect_video_tools_enabled() self._disable_planner_validation = not agent_cfg.planner_validation.enabled - self._planner_validation_threshold = agent_cfg.planner_validation.similarity_threshold self._enable_committee = agent_cfg.committee.enabled self._committee_debate_rounds = agent_cfg.committee.debate_rounds self._disable_checker = not agent_cfg.checker.enabled @@ -298,6 +298,25 @@ class AgentConfigBuilder: self._disable_device_probes = not device_probes return self + def with_verification_level(self, level: str) -> "AgentConfigBuilder": + """Apply a coarse Checker preset (the ``--verification-level`` knob). + + Presets are defined once in :data:`artemis.config.VERIFICATION_LEVEL_PRESETS`: + ``off`` (no Checker), ``final`` (exit review only, the factory default), + ``checkpoints`` (every plan checkpoint + exit review) and ``strict`` + (checkpoints with a larger repair budget; a failed assert halts). + Fields the preset leaves unspecified keep their current values, so an + explicit :meth:`with_checker` call afterwards still wins. + + Args: + level: One of ``off``, ``final``, ``checkpoints`` or ``strict`` + (case-insensitive, surrounding whitespace ignored). + + Raises: + ValueError: when ``level`` is not a known preset. + """ + return self.with_checker(**checker_overrides_for_level(level)) + def with_midway_checks(self, enabled: bool = True) -> "AgentConfigBuilder": """Enable or disable plan-declared midway checkpoints.""" self._disable_midway_checks = not enabled @@ -317,18 +336,17 @@ class AgentConfigBuilder: self._disable_planner_validation = disable return self - def with_planner_validation( - self, enabled: bool = True, similarity_threshold: float | None = None - ) -> "AgentConfigBuilder": - """Configure async Planner validation on milestone changes. + def with_planner_validation(self, enabled: bool = True) -> "AgentConfigBuilder": + """Configure the advisory async Planner validation on milestone changes. + + Every top-level milestone text change is reviewed by the lightweight + validator; a flagged change only yields a hint plus reason for the + Operator (no rollback, no action suppression). Args: enabled: Whether planner validation is enabled - similarity_threshold: Optional similarity threshold (0.0 - 1.0) """ self._disable_planner_validation = not enabled - if similarity_threshold is not None: - self._planner_validation_threshold = similarity_threshold return self def with_enable_committee(self, enabled: bool = True) -> "AgentConfigBuilder": @@ -381,8 +399,10 @@ class AgentConfigBuilder: """Configure ⚡ Flash execution profile options. Args: - max_turns: Maximum reactive loop turns before timeout - explorer_mode: Visual perception mode for Flash runner ('flash' 1-shot detection) + max_turns: Maximum reactive loop turns (0 = unlimited) + explorer_mode: Explorer tier used by the Flash runner's ``ask_explorer`` + ('flash' one-shot detection, 'pro' short reasoning loop, 'ultra' + deep loop); mirrored to ``explorer.flash_mode`` step_summarizer: Enable/disable asynchronous visual context compressor step_summarizer_model: Lightweight model for background step summarization prune_history_xml: Whether to prune outdated XML trees from historical steps @@ -430,15 +450,20 @@ class AgentConfigBuilder: committee: bool | None = None, checker: bool | None = None, video_ledger: bool | None = None, + verification_level: str | None = None, ) -> "AgentConfigBuilder": """Configure 🚀 Pro execution profile options. Args: - explorer_mode: Visual perception mode for Pro profile ('flash', 'pro', or 'ultra') - planner_validation: Enable/disable planner milestone validation + explorer_mode: Explorer tier used by the Pro profile's Operator / Validator + ('flash', 'pro', or 'ultra'); mirrored to ``explorer.pro_mode`` + planner_validation: Enable/disable the advisory review of plan milestone edits committee: Enable/disable multi-agent committee debate tool - checker: Enable/disable visual verification & snapshot rollback + checker: Enable/disable the Checker (plan checkpoint verification + exit final review) video_ledger: Enable/disable screen video action ledger tracking + verification_level: Coarse Checker preset ('off', 'final', 'checkpoints', + 'strict'); applied before the explicit ``checker`` switch so the + switch still wins when both are given """ if explorer_mode is not None: self._pro = self._pro.model_copy( @@ -449,6 +474,8 @@ class AgentConfigBuilder: self.with_planner_validation(enabled=planner_validation) if committee is not None: self.with_committee(enabled=committee) + if verification_level is not None: + self.with_verification_level(verification_level) if checker is not None: self.with_checker(enabled=checker) if video_ledger is not None: @@ -471,15 +498,23 @@ class AgentConfigBuilder: caching: bool | None = None, versions: dict[str, ExplorerVersion] | None = None, ) -> "AgentConfigBuilder": - """Configure UI Explorer visual perception options. + """Configure the Explorer tier behind ``ask_explorer``. + + The tier is a user setting resolved per execution profile; calling agents + never see it. Precedence at run time: ``ARTEMIS_EXPLORER_VERSION``, then + ``versions`` (per-agent override), then the profile knob (``flash_mode`` + for the Flash runner, ``pro_mode`` for the Pro Operator / Validator), then + ``default_version``. Args: - version: Default Explorer version mode ('flash', 'pro', or 'ultra') - default_version: Alias for version ('flash', 'pro', or 'ultra') - flash_mode: Explorer version for Flash execution profile (FlashRunner) - pro_mode: Explorer version for Pro execution profile (Graph/Operator/Validator) - caching: Whether to enable context caching for multi-turn Explorer - versions: Optional explicit per-agent version mapping + version: Fallback tier when no profile knob applies ('flash', 'pro', 'ultra') + default_version: Alias for version + flash_mode: Tier for the Flash execution profile (FlashRunner) + pro_mode: Tier for the Pro execution profile (Operator / Validator) + caching: Gemini explicit context caching for the multi-turn tiers; + None keeps the per-tier default (off for pro, on for ultra) + versions: Advanced per-agent override, e.g. ``{"validator": "ultra"}``; + empty by default so the profile knobs decide """ target_version = version if version is not None else default_version updates = {} @@ -498,12 +533,16 @@ class AgentConfigBuilder: return self def with_explorer_version(self, version: ExplorerVersion) -> "AgentConfigBuilder": - """Configure default model version to use for explorer sub-agents ('flash', 'pro', or 'ultra').""" + """Configure the fallback Explorer tier ('flash', 'pro', or 'ultra'). + + Only applies when no profile knob matches; prefer :meth:`with_flash_config` + / :meth:`with_pro_config` (or :meth:`with_explorer`) for the per-profile tiers. + """ self._explorer = self._explorer.model_copy(update={"default_version": version}) return self def with_explorer_versions(self, versions: dict[str, ExplorerVersion]) -> "AgentConfigBuilder": - """Configure per-agent model versions to use for explorer sub-agents.""" + """Configure the advanced per-agent Explorer tier override (empty by default).""" self._explorer_versions = versions return self @@ -580,7 +619,6 @@ class AgentConfigBuilder: assert_failure_policy=self._assert_failure_policy, disable_device_probes=self._disable_device_probes, disable_planner_validation=self._disable_planner_validation, - planner_validation_threshold=self._planner_validation_threshold, enable_committee=self._enable_committee, committee_debate_rounds=self._committee_debate_rounds, disable_outputter=self._disable_outputter, diff --git a/artemis/sdk/examples/video_transcription_example.py b/artemis/sdk/examples/video_transcription_example.py index af76c12..5b12673 100644 --- a/artemis/sdk/examples/video_transcription_example.py +++ b/artemis/sdk/examples/video_transcription_example.py @@ -38,8 +38,8 @@ def get_video_capable_llm_config() -> LLMConfig: """Returns an LLM config with video_analyzer configured. The video_analyzer must use a video-capable Gemini model: - - gemini-3.7-flash (recommended - fast and capable) - - gemini-3.6-flash + - gemini-3.8-flash (recommended - fast and capable) + - gemini-3.7-flash - gemini-3-pro-preview - gemini-2.5-flash - gemini-2.5-pro @@ -64,8 +64,8 @@ def get_video_capable_llm_config() -> LLMConfig: ), video_analyzer=LLMWithFallback( provider="google", - model="gemini-3.7-flash", - fallback=LLM(provider="google", model="gemini-3.6-flash"), + model="gemini-3.8-flash", + fallback=LLM(provider="google", model="gemini-3.7-flash"), ), ), ) diff --git a/artemis/sdk/types/agent.py b/artemis/sdk/types/agent.py index b823bdd..79a934a 100644 --- a/artemis/sdk/types/agent.py +++ b/artemis/sdk/types/agent.py @@ -123,7 +123,6 @@ class AgentConfig(BaseModel): assert_failure_policy: Literal["continue", "halt"] = "continue" disable_device_probes: bool = False disable_planner_validation: bool = False - planner_validation_threshold: float = 0.85 enable_committee: bool = False committee_debate_rounds: int = 2 disable_outputter: bool = False @@ -132,8 +131,10 @@ class AgentConfig(BaseModel): flash: FlashProfileConfig = Field(default_factory=FlashProfileConfig) pro: ProProfileConfig = Field(default_factory=ProProfileConfig) explorer: ExplorerConfig = Field(default_factory=ExplorerConfig) - explorer_versions: dict[str, Literal["flash", "pro", "ultra"]] - denylisted_tools: dict[str, list[str]] + # Advanced per-agent tier override; empty so the per-profile knobs + # (``explorer.flash_mode`` / ``explorer.pro_mode``) decide by default. + explorer_versions: dict[str, Literal["flash", "pro", "ultra"]] = Field(default_factory=dict) + denylisted_tools: dict[str, list[str]] = Field(default_factory=dict) enable_video_ledger: bool = True video_analyzer: VideoAnalyzerConfig = Field(default_factory=VideoAnalyzerConfig) concurrency_mode: Literal["global", "per_device"] = "per_device" diff --git a/artemis/services/llm.py b/artemis/services/llm.py index a89d87f..2c770ea 100644 --- a/artemis/services/llm.py +++ b/artemis/services/llm.py @@ -303,8 +303,8 @@ def _handle_llm_pause_and_resume(last_error: Exception) -> Path: llm_logger.warning(f"LLM Error: {err_msg}. Pausing execution to wait for resume signal...") pause_file = PAUSE_FILE try: - pause_file.write_text(f"LLM Error: {err_msg}") - except Exception: + pause_file.write_text(f"LLM Error: {err_msg}", encoding="utf-8") + except (OSError, UnicodeError): pass current_engine = _get_current_data_engine() @@ -896,7 +896,7 @@ async def invoke_llm_with_timeout_message[T]( # Backward compatible factory functions delegating to ModelFactory def get_google_llm( - model_name: str = "gemini-3.7-flash", + model_name: str = "gemini-3.8-flash", temperature: float | None = None, timeout: float | None = None, thinking_budget: int | None = None, @@ -918,7 +918,7 @@ def get_google_llm( def get_vertex_llm( - model_name: str = "gemini-3.7-flash", + model_name: str = "gemini-3.8-flash", temperature: float | None = None, timeout: float | None = None, thinking_budget: int | None = None, @@ -1029,8 +1029,13 @@ def _resolve_endpoint( if getattr(ctx, "llm_config", None) is None: try: ctx.llm_config = get_default_llm_config() - except Exception: - pass + except Exception as exc: # pylint: disable=broad-exception-caught + # Config loading has open-ended failure modes; surface the real cause + # instead of letting the attribute access below fail with a misleading + # AttributeError on the still-missing config. + raise RuntimeError( + f"Default LLM config could not be loaded while resolving '{name}': {exc}" + ) from exc cfg = ctx.llm_config.get_utils(name) if is_utils else ctx.llm_config.get_agent(name) diff --git a/artemis/services/token_meter.py b/artemis/services/token_meter.py index 2f8f364..1ff4214 100644 --- a/artemis/services/token_meter.py +++ b/artemis/services/token_meter.py @@ -113,9 +113,7 @@ class SessionTokenMeter: self.cache_hit_calls = 0 self.last_prompt_tokens = 0 - def record( - self, usage: dict[str, int], *, update_last_prompt: bool = True - ) -> dict[str, int]: + def record(self, usage: dict[str, int], *, update_last_prompt: bool = True) -> dict[str, int]: """Accumulates one call's usage; returns a session snapshot. ``update_last_prompt=False`` accumulates the totals without touching @@ -178,9 +176,7 @@ def record_llm_usage( if not session_id or usage is None: return None - snapshot = get_meter(session_id).record( - usage, update_last_prompt=update_last_prompt - ) + snapshot = get_meter(session_id).record(usage, update_last_prompt=update_last_prompt) payload: dict[str, Any] = { **usage, "context_base_tokens": usage["prompt_tokens"], diff --git a/artemis/toolchain/descriptors.py b/artemis/toolchain/descriptors.py index 2fd6a5b..4c6d5a3 100644 --- a/artemis/toolchain/descriptors.py +++ b/artemis/toolchain/descriptors.py @@ -16,6 +16,9 @@ from collections.abc import Callable from dataclasses import dataclass +import logging + +logger = logging.getLogger(__name__) def _get_embedded_adb() -> str | None: @@ -25,9 +28,9 @@ def _get_embedded_adb() -> str | None: if hasattr(adbutils, "adb_path"): return adbutils.adb_path() - except Exception: + except Exception as exc: # pylint: disable=broad-exception-caught # Optional dependency probe: not installed or no bundled binary. - pass + logger.debug("Embedded adb probe via adbutils skipped: %s", exc, exc_info=True) return None @@ -38,10 +41,10 @@ def _get_embedded_ffmpeg() -> str | None: if hasattr(imageio_ffmpeg, "get_ffmpeg_exe"): return imageio_ffmpeg.get_ffmpeg_exe() - except Exception: + except Exception as exc: # pylint: disable=broad-exception-caught # Optional dependency probe: not installed, or imageio_ffmpeg raises # RuntimeError when it ships no binary for this platform. - pass + logger.debug("Embedded ffmpeg probe via imageio_ffmpeg skipped: %s", exc, exc_info=True) return None diff --git a/artemis/toolchain/resolver.py b/artemis/toolchain/resolver.py index 2096677..e6dea6d 100644 --- a/artemis/toolchain/resolver.py +++ b/artemis/toolchain/resolver.py @@ -14,6 +14,7 @@ """Multi-tier cross-platform binary resolution and discovery engine.""" +import logging import os from pathlib import Path import shutil @@ -21,6 +22,8 @@ import shutil from artemis.platform import OSType, platform from artemis.toolchain.descriptors import TOOLS, ToolDescriptor +logger = logging.getLogger(__name__) + class ToolchainResolver: """Discovers, validates, and resolves platform-specific binaries with graceful fallbacks.""" @@ -102,10 +105,12 @@ class ToolchainResolver: resolved = str(Path(embedded).resolve()) self._cache[key] = resolved return resolved - except Exception: + except Exception as exc: # pylint: disable=broad-exception-caught # Probing an optional third-party embedded binary; any failure # simply means "tool not found" and the caller handles None. - pass + logger.debug( + "Embedded binary fallback for %s skipped: %s", desc.name, exc, exc_info=True + ) # Do not permanently cache negative lookups so dynamic installs are detected immediately self._cache.pop(key, None) diff --git a/artemis/tools/command_tool.py b/artemis/tools/command_tool.py index c8ce3e9..0f522b6 100644 --- a/artemis/tools/command_tool.py +++ b/artemis/tools/command_tool.py @@ -12,11 +12,35 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Universal ADB command and background task management tools for ARTEMIS.""" +"""Universal ADB command and background task management tools for ARTEMIS. + +State model +----------- +Every automation task owns one :class:`AdbTaskRegistry` (stored on its +:class:`~artemis.context.ArtemisContext`). The registry holds the running +background processes, the recently finished task logs, and the persistent +terminal environments. Keeping this per context matters because the MCP +daemon runs concurrent tasks for different devices inside one process: a +module-level registry would leak device A's tasks and completion notices into +device B's Operator prompt. A module-level default registry still exists for +callers that have no context (tests, ad-hoc scripts). + +Hang protection +--------------- +Commands are passed to ``adb shell`` as an argument, never through stdin, so +the remote shell exits as soon as the script does. ``stdin`` is closed +(``DEVNULL``) unless the caller asks for ``Interactive=True``; a command that +reads stdin therefore sees EOF instead of blocking forever. Anything that +outlives ``WaitMsBeforeAsync`` is moved to a background task with a bounded +log buffer, and :func:`shutdown_adb_background_tasks` kills whatever is still +running when the automation task ends. +""" import asyncio from asyncio.subprocess import Process as AsyncProcess +from collections import deque import os +import shlex from typing import Any, Literal import uuid @@ -36,39 +60,68 @@ from artemis.utils.logger import get_logger logger = get_logger(__name__) -# Global persistent Android shell environments -_PERSISTENT_ENVIRONMENTS: dict[str, dict[str, str]] = {} -# Global background tasks -_BACKGROUND_TASKS: dict[str, "BackgroundTask"] = {} -# Finished tasks history logs cache (to support Action='analyze' even after tasks are unregistered) -_FINISHED_TASKS_LOGS: dict[str, dict[str, Any]] = {} +#: Maximum number of finished task records kept per registry. +_FINISHED_TASKS_CAP = 50 +#: Maximum number of output lines retained for a background task. +_BACKGROUND_LOG_MAX_LINES = 20000 +#: Seconds to wait for a terminated process before killing it. +_TERMINATE_GRACE_SECONDS = 3.0 +_EXIT_CODE_MARKER = "===EXIT_CODE===" +_ENV_START_MARKER = "===ENV_START===" -# pylint: disable=too-many-arguments,too-many-positional-arguments -def _register_finished_task( - task_id: str, - command: str, - cwd: str | None, - terminal_id: str | None, - status: str, - exit_code: int | None, - output: str, -): - """Caches execution details for finished tasks.""" - _FINISHED_TASKS_LOGS[task_id] = { - "task_id": task_id, - "command": command, - "cwd": cwd, - "terminal_id": terminal_id, - "status": status, - "exit_code": exit_code, - "output": output, - "notified": False, +#: Variables that every Android shell already defines. They are never +#: re-exported into a persistent terminal: re-exporting PATH/HOME etc. is +#: pointless and some of them (BOOTCLASSPATH, LD_*) are long and fragile. +_PERSISTENT_ENV_EXCLUDE = frozenset( + { + "PATH", + "HOME", + "HOSTNAME", + "USER", + "LOGNAME", + "SHELL", + "TERM", + "PWD", + "OLDPWD", + "SHLVL", + "_", + "TMPDIR", + "HISTFILE", + "LD_LIBRARY_PATH", + "LD_PRELOAD", + "EXTERNAL_STORAGE", + "ASEC_MOUNTPOINT", + "DOWNLOAD_CACHE", } - # Keep memory bounded - if len(_FINISHED_TASKS_LOGS) > 10: - oldest_key = next(iter(_FINISHED_TASKS_LOGS)) - del _FINISHED_TASKS_LOGS[oldest_key] +) +_PERSISTENT_ENV_EXCLUDE_PREFIXES = ( + "ANDROID_", + "BOOTCLASSPATH", + "DEX2OAT", + "SYSTEMSERVER", + "STANDALONE_", +) + +#: Guidance shared by the tool descriptions: the commands that hang an +#: ``adb shell`` and how to bound them. +HANG_GUIDANCE = ( + "Streaming or open-ended commands (logcat, top, getevent, screenrecord," + " tail -f, an interactive shell) never return on their own: bound them" + " (logcat -d, top -n 1, --time-limit) or expect them to be moved to a" + " background task. Detach daemons explicitly with" + " `nohup CMD >/dev/null 2>&1 &`, otherwise the shell waits for them." +) + + +def _adb_binary() -> str: + """Resolves the adb executable through the shared toolchain resolver.""" + try: + from artemis.toolchain import toolchain + + return toolchain.find_adb() + except Exception: # pylint: disable=broad-exception-caught + return "adb" def _is_output_long(output: str) -> bool: @@ -88,19 +141,183 @@ def _format_long_output_response(_task_id: str, output: str, intro: str) -> str: ) -def _get_task_info(task_id: str, ctx: ArtemisContext) -> dict[str, Any] | None: - """Retrieves metadata and logs for a running or finished task.""" - if task_id in _BACKGROUND_TASKS: - t = _BACKGROUND_TASKS[task_id] - return { - "command": t.command, - "cwd": t.cwd, - "terminal_id": t.terminal_id, - "status": t.status, - "output": "".join(t.stdout_log), +def _filter_persistent_env(env: dict[str, str]) -> dict[str, str]: + """Drops the platform-provided variables from a captured shell environment.""" + kept: dict[str, str] = {} + for key, value in env.items(): + if not key or key in _PERSISTENT_ENV_EXCLUDE: + continue + if key.startswith(_PERSISTENT_ENV_EXCLUDE_PREFIXES): + continue + if not key.replace("_", "a").isalnum(): + continue + kept[key] = value + return kept + + +# --------------------------------------------------------------------------- +# Registry +# --------------------------------------------------------------------------- + + +class AdbTaskRegistry: + """Per-task bookkeeping for background processes, finished logs and envs.""" + + def __init__(self) -> None: + self.background: dict[str, BackgroundTask] = {} + self.finished: dict[str, dict[str, Any]] = {} + self.persistent_envs: dict[str, dict[str, str]] = {} + + # -- finished task cache ------------------------------------------------- + + # pylint: disable=too-many-arguments,too-many-positional-arguments + def register_finished( + self, + task_id: str, + command: str, + cwd: str | None, + terminal_id: str | None, + status: str, + exit_code: int | None, + output: str, + ) -> None: + """Caches execution details for a finished task.""" + self.finished[task_id] = { + "task_id": task_id, + "command": command, + "cwd": cwd, + "terminal_id": terminal_id, + "status": status, + "exit_code": exit_code, + "output": output, + "notified": False, } - if task_id in _FINISHED_TASKS_LOGS: - return _FINISHED_TASKS_LOGS[task_id] + while len(self.finished) > _FINISHED_TASKS_CAP: + oldest_key = next(iter(self.finished)) + del self.finished[oldest_key] + + def get_task_info(self, task_id: str) -> dict[str, Any] | None: + """Returns metadata and logs for a running or finished task, if known.""" + if task_id in self.background: + t = self.background[task_id] + return { + "command": t.command, + "cwd": t.cwd, + "terminal_id": t.terminal_id, + "status": t.status, + "output": "".join(t.stdout_log), + } + return self.finished.get(task_id) + + # -- prompt helpers ------------------------------------------------------ + + def active_task_summaries(self) -> list[dict[str, Any]]: + """Compact view of running tasks for the Operator prompt.""" + return [ + { + "task_id": tid, + "command": t.command, + "cwd": t.cwd, + "terminal_id": t.terminal_id, + "output_line_count": len(t.stdout_log), + } + for tid, t in self.background.items() + ] + + def pop_unnotified_finished(self) -> list[dict[str, Any]]: + """Returns finished tasks not yet shown to the model and marks them shown.""" + newly_finished: list[dict[str, Any]] = [] + for tid, tinfo in self.finished.items(): + if tinfo.get("notified", False): + continue + newly_finished.append( + { + "task_id": tid, + "command": tinfo.get("command", ""), + "status": tinfo.get("status", "completed"), + "output_text": tinfo.get("output", ""), + } + ) + tinfo["notified"] = True + return newly_finished + + # -- lifecycle ----------------------------------------------------------- + + async def shutdown(self, ctx: ArtemisContext | None = None) -> int: + """Terminates every running background task. Returns the number killed.""" + tasks = list(self.background.values()) + for task in tasks: + try: + await task.stop(ctx, status="killed") + except Exception as e: # pylint: disable=broad-exception-caught + logger.warning(f"Failed to stop background ADB task {task.task_id}: {e}") + return len(tasks) + + +#: Registry used when no context is available. +_DEFAULT_REGISTRY = AdbTaskRegistry() + +# Backward-compatibility aliases onto the default registry's dictionaries. +_BACKGROUND_TASKS = _DEFAULT_REGISTRY.background +_FINISHED_TASKS_LOGS = _DEFAULT_REGISTRY.finished +_PERSISTENT_ENVIRONMENTS = _DEFAULT_REGISTRY.persistent_envs + + +def get_adb_task_registry(ctx: ArtemisContext | None) -> AdbTaskRegistry: + """Returns the registry bound to ``ctx``, creating it on first use.""" + if ctx is None: + return _DEFAULT_REGISTRY + existing = getattr(ctx, "adb_task_registry", None) + if isinstance(existing, AdbTaskRegistry): + return existing + registry = AdbTaskRegistry() + try: + setattr(ctx, "adb_task_registry", registry) + except (AttributeError, TypeError): + return _DEFAULT_REGISTRY + return registry + + +async def shutdown_adb_background_tasks(ctx: ArtemisContext | None) -> int: + """Kills the background ADB tasks of ``ctx``. Safe to call when none exist.""" + existing = getattr(ctx, "adb_task_registry", None) if ctx is not None else None + if not isinstance(existing, AdbTaskRegistry): + return 0 + killed = await existing.shutdown(ctx) + if killed: + logger.info(f"Terminated {killed} background ADB task(s) at task end.") + return killed + + +# pylint: disable=too-many-arguments,too-many-positional-arguments +def _register_finished_task( + task_id: str, + command: str, + cwd: str | None, + terminal_id: str | None, + status: str, + exit_code: int | None, + output: str, + ctx: ArtemisContext | None = None, +): + """Caches execution details for finished tasks (registry of ``ctx``).""" + get_adb_task_registry(ctx).register_finished( + task_id, command, cwd, terminal_id, status, exit_code, output + ) + + +def _get_task_info(task_id: str, ctx: ArtemisContext | None) -> dict[str, Any] | None: + """Retrieves metadata and logs for a running or finished task. + + Looks in the context registry, then the default registry, then the + persisted background task table. + """ + registry = get_adb_task_registry(ctx) + info = registry.get_task_info(task_id) + if info is None and registry is not _DEFAULT_REGISTRY: + info = _DEFAULT_REGISTRY.get_task_info(task_id) + if info is not None: + return info if ctx and ctx.data_engine: db_tasks = ctx.data_engine.get_all_background_tasks() for db_t in db_tasks: @@ -115,6 +332,11 @@ def _get_task_info(task_id: str, ctx: ArtemisContext) -> dict[str, Any] | None: return None +# --------------------------------------------------------------------------- +# Background task +# --------------------------------------------------------------------------- + + # pylint: disable=too-many-instance-attributes class BackgroundTask: """Represents an active or completing background ADB shell task.""" @@ -127,89 +349,148 @@ class BackgroundTask: process: AsyncProcess, terminal_id: str | None = None, cwd: str | None = None, + interactive: bool = False, + registry: AdbTaskRegistry | None = None, ): self.task_id = task_id self.command = command self.process = process self.terminal_id = terminal_id self.cwd = cwd - self.stdout_log: list[str] = [] + self.interactive = interactive + self.registry = registry + self.stdout_log: deque[str] = deque(maxlen=_BACKGROUND_LOG_MAX_LINES) self.status = "running" self.exit_code: int | None = None self.trace_id: uuid.UUID | None = None self.listener_task: asyncio.Task | None = None + self._stop_requested = False + self._finalized = False async def start(self, ctx: ArtemisContext): """Starts the background output listener task.""" self.listener_task = asyncio.create_task(self._listen(ctx)) + async def stop(self, ctx: ArtemisContext | None, status: str = "killed") -> None: + """Terminates the process (kill after a grace period) and records the result.""" + self._stop_requested = True + self.status = status + proc = self.process + if getattr(proc, "returncode", None) is None: + try: + proc.terminate() + except (ProcessLookupError, OSError): + pass + try: + await asyncio.wait_for(proc.wait(), timeout=_TERMINATE_GRACE_SECONDS) + except TimeoutError: + try: + proc.kill() + except (ProcessLookupError, OSError): + pass + try: + await asyncio.wait_for(proc.wait(), timeout=_TERMINATE_GRACE_SECONDS) + except TimeoutError: + pass + except Exception as exc: # pylint: disable=broad-exception-caught + # Shutdown must never raise; the exit code is read back below. + logger.debug( + f"Waiting for background ADB task {self.task_id} to exit failed: {exc}", + exc_info=True, + ) + if self.listener_task is not None and not self.listener_task.done(): + self.listener_task.cancel() + try: + await self.listener_task + except asyncio.CancelledError: + pass + self.exit_code = getattr(proc, "returncode", None) + self._finalize(ctx) + + def _finalize(self, ctx: ArtemisContext | None) -> None: + """Moves the task from the active map to the finished cache exactly once.""" + if self._finalized: + return + self._finalized = True + registry = self.registry or get_adb_task_registry(ctx) + + output_text = "".join(self.stdout_log) + _, parsed_env, _ = self.parse_output(output_text) + if self.terminal_id and parsed_env is not None: + registry.persistent_envs[self.terminal_id] = _filter_persistent_env(parsed_env) + logger.info( + "Updated persistent env for Android terminal" + f" '{self.terminal_id}' from background task." + ) + + if ctx and getattr(ctx, "data_engine", None): + try: + ctx.data_engine.unregister_background_task(self.task_id, self.status) + except Exception as e: # pylint: disable=broad-exception-caught + logger.debug(f"Failed to unregister background task {self.task_id}: {e}") + + registry.register_finished( + task_id=self.task_id, + command=self.command, + cwd=self.cwd, + terminal_id=self.terminal_id, + status=self.status, + exit_code=self.exit_code, + output=output_text, + ) + registry.background.pop(self.task_id, None) + async def _listen(self, ctx: ArtemisContext): """Reads process output streams and updates lifecycle status.""" try: - # Read stdout/stderr merged while True: line = await self.process.stdout.readline() if not line: break - decoded = line.decode() + decoded = line.decode(errors="replace") self.stdout_log.append(decoded) - if ctx and ctx.data_engine and getattr(self, "trace_id", None): + if ctx and getattr(ctx, "data_engine", None) and self.trace_id: ctx.data_engine.stream_output(self.trace_id, decoded) self.exit_code = await self.process.wait() + if self._stop_requested: + return + _, _, script_exit = self.parse_output("".join(self.stdout_log)) + if self.terminal_id and script_exit is not None: + self.exit_code = script_exit self.status = "completed" if self.exit_code == 0 else "failed" logger.info( f"Background ADB task {self.task_id} completed with exit code {self.exit_code}" ) - - # Post-process output to extract env if persistent - output_text = "".join(self.stdout_log) - _, parsed_env = self.parse_output(output_text) - - if self.terminal_id and parsed_env: - _PERSISTENT_ENVIRONMENTS[self.terminal_id] = parsed_env - logger.info( - "Updated persistent env for Android terminal" - f" '{self.terminal_id}' from background task." - ) - - # Update status in DataEngine - if ctx and ctx.data_engine: - ctx.data_engine.unregister_background_task( - task_id=self.task_id, - status=self.status, - ) + except asyncio.CancelledError: + return except Exception as e: # pylint: disable=broad-exception-caught logger.error(f"Error in background task {self.task_id} listener: {e}") self.status = "error" - if ctx and ctx.data_engine: - ctx.data_engine.unregister_background_task(self.task_id, "failed") finally: - # Cache logs in memory before removing task from active tasks - full_output = "".join(self.stdout_log) - _register_finished_task( - task_id=self.task_id, - command=self.command, - cwd=self.cwd, - terminal_id=self.terminal_id, - status=self.status, - exit_code=self.exit_code, - output=full_output, - ) - if self.task_id in _BACKGROUND_TASKS: - del _BACKGROUND_TASKS[self.task_id] + if not self._stop_requested: + self._finalize(ctx) @staticmethod - def parse_output(output: str) -> tuple[str, dict[str, str] | None]: - """Separates stdout lines from exported environment variables.""" - clean_lines = [] - env_lines = [] + def parse_output(output: str) -> tuple[str, dict[str, str] | None, int | None]: + """Separates stdout lines from the exit-code marker and exported env. + + Returns ``(clean_output, env_or_None, script_exit_code_or_None)``. + """ + clean_lines: list[str] = [] + env_lines: list[str] = [] + exit_code: int | None = None in_env = False for line in output.splitlines(): - if "===EXIT_CODE===" in line: + if _EXIT_CODE_MARKER in line: + raw = line.split(_EXIT_CODE_MARKER, 1)[1].strip() + try: + exit_code = int(raw) + except ValueError: + exit_code = None continue - if "===ENV_START===" in line: + if _ENV_START_MARKER in line: in_env = True continue if in_env: @@ -219,20 +500,25 @@ class BackgroundTask: clean_output = "\n".join(clean_lines) - parsed_env = None - if env_lines: + parsed_env: dict[str, str] | None = None + if in_env: parsed_env = {} for el in env_lines: if "=" in el: k, v = el.split("=", 1) parsed_env[k] = v - return clean_output, parsed_env + return clean_output, parsed_env, exit_code # Backwards compatibility alias _parse_output = parse_output +# --------------------------------------------------------------------------- +# run_adb_command +# --------------------------------------------------------------------------- + + class RunAdbCommandArgs(BaseModel): """Arguments schema for executing ADB shell commands.""" @@ -268,6 +554,35 @@ class RunAdbCommandArgs(BaseModel): " synchronously before sending it to the background." ), ) + Interactive: bool = Field( + default=False, + description=( + "Keep stdin open so 'manage_task send_input' can feed the command" + " later. Default false: stdin is closed and commands that read it" + " see EOF instead of hanging." + ), + ) + + +def _build_phone_script( + cmd_line: str, + cwd: str, + env_vars: dict[str, str], + run_persistent: bool, +) -> str: + """Builds the script passed as the single ``adb shell`` argument.""" + script = f"cd {shlex.quote(cwd)}\n" + for k, v in env_vars.items(): + script += f"export {k}={shlex.quote(v)}\n" + script += f"{cmd_line}\n" + if run_persistent: + script += ( + "_artemis_ec=$?\n" + f'echo "{_EXIT_CODE_MARKER}$_artemis_ec"\n' + f'echo "{_ENV_START_MARKER}"\n' + "env\n" + ) + return script class RunAdbCommandTool(ArtemisTool): @@ -278,10 +593,10 @@ class RunAdbCommandTool(ArtemisTool): name="run_adb_command", description=( "[SHELL] Executes a shell command directly on the Android mobile " - "device via ADB shell. Can run synchronously or transition to a " + "device via ADB shell. Runs synchronously and transitions to a " "background task if execution takes longer than WaitMsBeforeAsync. " "Supports persistent environments (environment variables) on the " - "phone across invocations." + f"phone across invocations. {HANG_GUIDANCE}" ), args_schema=RunAdbCommandArgs, category="system", @@ -297,6 +612,7 @@ class RunAdbCommandTool(ArtemisTool): RunPersistent: bool = False, RequestedTerminalID: str | None = None, WaitMsBeforeAsync: int = 5000, + Interactive: bool = False, **kwargs: Any, ) -> str: # Extract command line supporting alternative key formats @@ -319,6 +635,8 @@ class RunAdbCommandTool(ArtemisTool): if WaitMsBeforeAsync != 5000 else kwargs.get("wait_ms_before_async", WaitMsBeforeAsync) ) + interactive = bool(Interactive or kwargs.get("interactive", False)) + wait_seconds = max(float(wait_ms) / 1000.0, 0.05) # Fallback to driver's execute_shell if no full ArtemisContext is available if ( @@ -327,7 +645,9 @@ class RunAdbCommandTool(ArtemisTool): and hasattr(driver, "execute_shell") and not run_persistent ): - return await driver.execute_shell(cmd_line) + return await driver.execute_shell(cmd_line, timeout_seconds=wait_seconds) + + registry = get_adb_task_registry(ctx) device_id = None if ctx and hasattr(ctx, "device") and ctx.device: @@ -340,53 +660,54 @@ class RunAdbCommandTool(ArtemisTool): # Prepare environment terminal_id = None run_env = os.environ.copy() # Local host env to run adb command - android_env_vars = {} + android_env_vars: dict[str, str] = {} if run_persistent: terminal_id = requested_terminal_id or f"term_{uuid.uuid4().hex[:8]}" - if terminal_id in _PERSISTENT_ENVIRONMENTS: - android_env_vars = _PERSISTENT_ENVIRONMENTS[terminal_id] - else: - _PERSISTENT_ENVIRONMENTS[terminal_id] = android_env_vars + android_env_vars = registry.persistent_envs.setdefault(terminal_id, {}) - # Construct command to run inside adb shell - # We inject the cached environment variables by prepending 'export K=V' declarations - env_prepends = "" - for k, v in android_env_vars.items(): - env_prepends += f"export {k}='{v}'\n" + phone_script = _build_phone_script(cmd_line, cwd, android_env_vars, run_persistent) - # Construct final command script - phone_script = f"cd '{cwd}'\n" + env_prepends + f"{cmd_line}\n" - if run_persistent: - phone_script += ( - '_exit_code=$?\necho "===EXIT_CODE===$_exit_code"\necho "===ENV_START==="\nenv' - ) - - # In Cloud Mode or when adb client is virtualized + # In Cloud Mode or when adb client is virtualized: no host subprocess, + # therefore no background handoff. The wait window is a hard timeout. adb_client = getattr(ctx, "adb_client", None) if ctx else None if os.environ.get("ARTEMIS_CLOUD_MODE") == "1" or ( adb_client is not None and hasattr(adb_client, "_bridge") ): try: device = get_adb_device(ctx) - output = device.shell(phone_script) - clean_output, new_env = BackgroundTask.parse_output(output) - if run_persistent and new_env: - android_env_vars.update(new_env) + output = await asyncio.wait_for( + asyncio.to_thread(device.shell, phone_script), + timeout=wait_seconds, + ) + except TimeoutError: return ( - clean_output - if clean_output.strip() - else "Command executed successfully with empty output." + f"Error: Command did not finish within {wait_seconds:.1f}s." + " Background tasks are not available on this device" + " connection; bound the command (logcat -d, top -n 1," + " --time-limit) and retry." ) except Exception as e: # pylint: disable=broad-exception-caught logger.error(f"Failed to execute virtual adb shell for '{cmd_line}': {e}") return f"Failed to execute adb command: {e}" + clean_output, new_env, script_exit = BackgroundTask.parse_output(str(output)) + if run_persistent and new_env is not None: + registry.persistent_envs[terminal_id] = _filter_persistent_env(new_env) + intro = "ADB command completed" + if script_exit is not None: + intro += f" with exit code {script_exit}" + intro += "." + if run_persistent: + intro += f" TerminalID: {terminal_id}." + body = clean_output if clean_output.strip() else "(empty output)" + return f"{intro}\nOutput:\n{body}" - # We spawn the host subprocess: adb -s shell - # Pass phone_script as an argument so it doesn't hold the shell open if we keep stdin open. + # We spawn the host subprocess: adb -s shell