From 2fea9865ee0ba76305536c41b20479095f62bf27 Mon Sep 17 00:00:00 2001 From: somew1nd Date: Thu, 3 Sep 2026 16:05:47 -0700 Subject: [PATCH] feat: unify history tools and improve execution reliability --- README.md | 4 +- README_CN.md | 4 +- .../repositories/session_repository.py | 84 +++ apps/admin_console/routers/sessions.py | 9 + apps/admin_console/services/media_service.py | 75 ++- apps/showcase_ui/package-lock.json | 2 +- .../agent-stream/agent-stream.component.html | 86 ++- .../agent-stream/agent-stream.component.scss | 158 ++++- .../agent-stream/agent-stream.component.ts | 92 ++- .../floating-video-player.component.ts | 24 +- .../src/app/core/models/session.model.ts | 22 + .../src/app/core/models/stream.model.ts | 32 +- .../src/app/services/agent.service.spec.ts | 36 ++ .../src/app/services/agent.service.ts | 75 ++- .../app/utils/recording-timeline.util.spec.ts | 107 ++++ .../src/app/utils/recording-timeline.util.ts | 123 ++++ .../src/app/utils/run-info.util.spec.ts | 92 +++ .../src/app/utils/run-info.util.ts | 116 ++++ .../app/utils/stream-aggregator.util.spec.ts | 112 +++- .../src/app/utils/stream-aggregator.util.ts | 86 ++- .../src/app/utils/tool-formatter.util.ts | 35 +- artemis/agents/checker/checker.py | 264 ++++---- artemis/agents/diagnoser/diagnoser.py | 40 +- artemis/agents/flash/context_compressor.py | 24 +- artemis/agents/flash/flash_runner.md | 4 +- artemis/agents/flash/runner.py | 49 +- .../history_analyzer/history_analyzer.md | 4 +- .../history_analyzer/history_analyzer.py | 188 ++---- artemis/agents/operator/operator.json | 2 +- artemis/agents/operator/operator.py | 27 +- artemis/agents/operator/prompts.py | 31 +- artemis/agents/outputter/outputter.py | 58 +- artemis/agents/outputter/tools.py | 170 ----- artemis/agents/summarizer/summarizer.md | 2 +- artemis/agents/summarizer/summarizer.py | 13 +- artemis/agents/validator/execution_loop.py | 123 +++- artemis/agents/validator/pixel_safety_net.md | 2 +- artemis/agents/validator/precondition_xml.py | 55 +- artemis/config/__init__.py | 6 + artemis/config/agent.py | 73 ++- artemis/controllers/unified_controller.py | 29 +- artemis/data_engine/engine.py | 535 ++++++++++------ artemis/data_engine/history_reader.py | 142 +++++ artemis/data_engine/storage.py | 17 +- artemis/graph/checkpoints.py | 120 +++- artemis/graph/graph.py | 6 +- artemis/interfaces/cli/commands/ui.py | 124 +++- artemis/mcp/action_executor.py | 51 +- artemis/mcp/action_manifest.py | 4 +- artemis/memory/chunking.py | 349 +++++++---- artemis/memory/context_policy.py | 18 +- artemis/memory/step_capsule.md | 40 +- artemis/memory/transcript.py | 125 +++- artemis/resources/config/artemis.jsonc | 9 +- artemis/sdk/agent.py | 31 +- artemis/services/token_meter.py | 14 + artemis/tools/base.py | 133 ++-- artemis/tools/committee_prompts.json | 4 +- artemis/tools/committee_tool.py | 29 +- artemis/tools/history/__init__.py | 397 ++++++++++++ artemis/tools/history/replay.py | 115 ++++ artemis/tools/history/screenshot.py | 128 ++++ artemis/tools/history/search.py | 396 ++++++++++++ artemis/tools/history_recall.py | 590 ------------------ artemis/tools/tool_wrapper.py | 106 +++- artemis/utils/coordinates.py | 36 +- artemis/utils/python_executor.py | 206 ++++-- artemis/utils/task_tree.py | 118 +++- artemis/utils/video.py | 43 +- artemis/utils/visualization.py | 41 ++ config/artemis.jsonc | 9 +- mcp_server/rules.md | 2 +- mcp_server/tools/inspect_trace.py | 280 ++++----- ...st_explorer_ask_image_processor_jupyter.py | 58 +- .../test_history_analyzer_tools.py | 65 +- tests/tools/outputter/test_outputter_tools.py | 41 +- .../test_media_recording_recovery.py | 59 ++ .../test_session_checks_endpoint.py | 22 +- .../test_session_usage_endpoint.py | 119 ++++ tests/unit/agents/test_checker.py | 310 ++++++++- tests/unit/agents/test_flash_runner.py | 67 ++ tests/unit/agents/test_flash_runner_ledger.py | 57 +- .../unit/agents/test_historical_state_hint.py | 2 +- tests/unit/agents/test_history_analyzer.py | 37 +- tests/unit/agents/test_operator.py | 6 + tests/unit/agents/test_operator_prompts.py | 8 +- tests/unit/agents/test_operator_transcript.py | 31 +- .../unit/agents/test_outputter_tools_unit.py | 102 --- .../agents/test_precondition_pixel_target.py | 3 + tests/unit/agents/test_prompt_assembly.py | 11 +- tests/unit/agents/test_summarizer.py | 26 +- tests/unit/agents/test_validator.py | 201 +++++- tests/unit/data_engine/test_engine.py | 191 ++++++ tests/unit/data_engine/test_offline_reader.py | 139 +++++ tests/unit/graph/test_checkpoints.py | 92 +++ .../mcp/test_action_executor_helper_tools.py | 73 ++- tests/unit/mcp/test_mcp_tools.py | 153 +++++ tests/unit/memory/test_history_chunking.py | 306 ++++++++- tests/unit/memory/test_transcript_ledger.py | 98 +++ tests/unit/sdk/test_run_tuning_summary.py | 41 ++ tests/unit/test_config_package.py | 39 ++ tests/unit/test_llm_gateway.py | 1 - tests/unit/test_memory_config.py | 11 +- tests/unit/test_token_meter.py | 22 + tests/unit/test_ui_command.py | 117 ++++ tests/unit/test_unified_controller_video.py | 82 +++ tests/unit/tools/history/test_declarations.py | 99 +++ tests/unit/tools/history/test_replay.py | 150 +++++ .../tools/history/test_result_messages.py | 120 ++++ tests/unit/tools/history/test_screenshot.py | 109 ++++ tests/unit/tools/history/test_search.py | 334 ++++++++++ tests/unit/tools/test_committee_tool.py | 2 +- tests/unit/tools/test_history_recall.py | 268 -------- tests/unit/utils/test_coordinate_space.py | 104 +++ tests/unit/utils/test_task_tree.py | 130 ++++ tests/unit/utils/test_video_manifest.py | 132 ++++ 116 files changed, 8291 insertions(+), 2503 deletions(-) create mode 100644 apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts create mode 100644 apps/showcase_ui/src/app/utils/recording-timeline.util.ts create mode 100644 apps/showcase_ui/src/app/utils/run-info.util.spec.ts create mode 100644 apps/showcase_ui/src/app/utils/run-info.util.ts delete mode 100644 artemis/agents/outputter/tools.py create mode 100644 artemis/data_engine/history_reader.py create mode 100644 artemis/tools/history/__init__.py create mode 100644 artemis/tools/history/replay.py create mode 100644 artemis/tools/history/screenshot.py create mode 100644 artemis/tools/history/search.py delete mode 100644 artemis/tools/history_recall.py create mode 100644 tests/unit/admin_console/test_session_usage_endpoint.py delete mode 100644 tests/unit/agents/test_outputter_tools_unit.py create mode 100644 tests/unit/data_engine/test_offline_reader.py create mode 100644 tests/unit/sdk/test_run_tuning_summary.py create mode 100644 tests/unit/tools/history/test_declarations.py create mode 100644 tests/unit/tools/history/test_replay.py create mode 100644 tests/unit/tools/history/test_result_messages.py create mode 100644 tests/unit/tools/history/test_screenshot.py create mode 100644 tests/unit/tools/history/test_search.py delete mode 100644 tests/unit/tools/test_history_recall.py create mode 100644 tests/unit/utils/test_coordinate_space.py create mode 100644 tests/unit/utils/test_video_manifest.py diff --git a/README.md b/README.md index f76cf43..905af3d 100644 --- a/README.md +++ b/README.md @@ -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): 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. +* **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 `search_history` / `replay_steps`) 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** vet only their first member and fire the rest 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 454ad45..c7e480c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -275,8 +275,8 @@ if __name__ == "__main__": ARTEMIS 提供两种运行模式以适应不同的自动化需求: -* **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]` 持续监控以及可选的书面报告。 +* **Flash 模式 (`--profile flash`)**:轻量极速且省 Token 的响应式循环(单步约 3–5 秒):单个模型观察实时屏幕、思考、执行,不经过图编排,适合常规确定性 UI 操作。默认不限步数(`agent.flash.max_turns`,0 表示不限),因为历史是被压缩而不是被截断:Flash 与 Pro 共用同一套会话记录账本(相对测试时间 `T+mm:ss`、截图折叠为视觉摘要、更早的步骤分块归档并可通过 `search_history` / `replay_steps` 按需召回),并可调用 `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/database/repositories/session_repository.py b/apps/admin_console/database/repositories/session_repository.py index 5fe61fb..a12e869 100644 --- a/apps/admin_console/database/repositories/session_repository.py +++ b/apps/admin_console/database/repositories/session_repository.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import json import logging import os import sqlite3 @@ -301,6 +302,89 @@ class SessionRepository: result.setdefault(session_id, []).append(name) return result + def get_session_usage(self, session_id: str) -> dict[str, Any]: + """Aggregate measured LLM usage for one session. + + Sums every ``llm_usage`` trace the token meter recorded (all agents, + Flash and Pro alike) and reports the prompt size of the latest + Operator/FlashRunner call as the live executor context. Also echoes + the profile and Pro tuning the run was started with, read from the + session's stored ``device_info``. + """ + from artemis.services.token_meter import ( + OPERATOR_CONTEXT_WINDOW_TOKENS, + OPERATOR_NODE_NAMES, + ) + + def _int(value: Any) -> int: + try: + return int(value or 0) + except (TypeError, ValueError): + return 0 + + llm_calls = prompt = completion = total = cached = 0 + operator_context: int | None = None + operator_context_at: float | None = None + with db_session(self.db_path) as conn: + cursor = conn.cursor() + cursor.execute( + "SELECT payload, timestamp FROM traces WHERE session_id = ?" + " AND type = 'llm_call' AND name = 'llm_usage' ORDER BY timestamp ASC", + (session_id,), + ) + for row in cursor.fetchall(): + raw = row["payload"] + try: + payload = json.loads(raw) if isinstance(raw, str) else raw + except (ValueError, TypeError): + continue + if not isinstance(payload, dict): + continue + p = _int(payload.get("prompt_tokens")) + c = _int(payload.get("completion_tokens")) + t = _int(payload.get("total_tokens")) or (p + c) + if t <= 0: + continue + llm_calls += 1 + prompt += p + completion += c + total += t + cached += _int(payload.get("cached_tokens")) + node = str(payload.get("node") or "").strip().lower() + if node in OPERATOR_NODE_NAMES: + operator_context = _int(payload.get("context_base_tokens")) or p + operator_context_at = row["timestamp"] + + cursor.execute("SELECT device_info FROM sessions WHERE session_id = ?", (session_id,)) + session_row = cursor.fetchone() + + profile: str | None = None + run_tuning: dict[str, Any] | None = None + if session_row and session_row["device_info"]: + try: + d_info = json.loads(session_row["device_info"]) + except (ValueError, TypeError): + d_info = None + if isinstance(d_info, dict): + profile = d_info.get("profile") or None + tuning = d_info.get("run_tuning") + if isinstance(tuning, dict) and tuning: + run_tuning = dict(tuning) + + return { + "session_id": session_id, + "llm_calls": llm_calls, + "prompt_tokens": prompt, + "completion_tokens": completion, + "total_tokens": total, + "cached_tokens": cached, + "operator_context_tokens": operator_context, + "operator_context_window_tokens": OPERATOR_CONTEXT_WINDOW_TOKENS, + "operator_context_updated_at": operator_context_at, + "profile": profile, + "run_tuning": run_tuning, + } + def get_session_by_id(self, session_id: str) -> dict[str, Any] | None: try: with db_session(self.db_path) as conn: diff --git a/apps/admin_console/routers/sessions.py b/apps/admin_console/routers/sessions.py index d270e7f..6d828b7 100644 --- a/apps/admin_console/routers/sessions.py +++ b/apps/admin_console/routers/sessions.py @@ -176,6 +176,15 @@ async def get_session_details(session_id: str): return dict(row) +@router.get("/api/sessions/{session_id}/usage") +async def get_session_usage(session_id: str): + """Session-wide LLM token totals, live executor context size and run tuning.""" + try: + return await asyncio.to_thread(session_repo.get_session_usage, session_id) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + @router.get("/api/sessions/{session_id}/tree") async def get_tree(session_id: str): try: diff --git a/apps/admin_console/services/media_service.py b/apps/admin_console/services/media_service.py index adff66c..e114d04 100644 --- a/apps/admin_console/services/media_service.py +++ b/apps/admin_console/services/media_service.py @@ -397,15 +397,19 @@ class MediaService: or not segment_path.is_file() ): continue - segments.append( - { - "url": cls.path_to_video_url(segment_path), - "start": float(item.get("start", 0)), - "duration": float(item.get("duration", 0)), - "width": int(item.get("width", 0)), - "height": int(item.get("height", 0)), - } - ) + segment = { + "url": cls.path_to_video_url(segment_path), + "start": float(item.get("start", 0)), + "duration": float(item.get("duration", 0)), + "width": int(item.get("width", 0)), + "height": int(item.get("height", 0)), + } + # Manifest v2 carries the real session-relative timeline so the + # UI can seek by step time across scrcpy restart gaps. + if item.get("offset_ms") is not None and item.get("duration_ms") is not None: + segment["offset_ms"] = int(item["offset_ms"]) + segment["duration_ms"] = int(item["duration_ms"]) + segments.append(segment) return segments except (OSError, KeyError, TypeError, ValueError, json.JSONDecodeError): return [] @@ -535,30 +539,43 @@ class MediaService: return f"Error reading task plan: {e}" return "No task plan created yet." + @staticmethod + def _read_jsonl_records(path: Path, label: str, session_id: str) -> list[dict[str, Any]]: + records: list[dict[str, Any]] = [] + if not path.is_file(): + return records + try: + for line in 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 {label} for {session_id}: {e}") + return records + @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". + verdict ledger (``check_ledger.jsonl``), the per-attempt transcripts of + the Checker's streamed reasoning (``check_streams.jsonl``: one record + per attempt with timestamped ``segments``) and the machine-readable run + outcome (``run_outcome.json``), all written by the Checker / + ``artemis.graph.checkpoints``. Missing files mean "no checks ran" + (sessions recorded before transcripts existed simply have no streams). """ 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}") + records = MediaService._read_jsonl_records( + session_dir / "check_ledger.jsonl", "check ledger", session_id + ) + streams = MediaService._read_jsonl_records( + session_dir / "check_streams.jsonl", "check streams", session_id + ) run_outcome: dict[str, Any] | None = None outcome_path = session_dir / "run_outcome.json" @@ -570,7 +587,7 @@ class MediaService: except (OSError, ValueError) as e: logger.warning(f"Failed to read run outcome for {session_id}: {e}") - return {"records": records, "run_outcome": run_outcome} + return {"records": records, "streams": streams, "run_outcome": run_outcome} @staticmethod def get_session_notes_content(session_id: str) -> dict[str, str]: diff --git a/apps/showcase_ui/package-lock.json b/apps/showcase_ui/package-lock.json index b47bcff..bab81b3 100644 --- a/apps/showcase_ui/package-lock.json +++ b/apps/showcase_ui/package-lock.json @@ -224,7 +224,7 @@ }, "node_modules/@angular/cdk": { "version": "22.1.5", - "resolved": "http://airlock-proxy.uplink.goog:999/npm/artifact-foundry-prod/ah-3p-staging-npm/@angular/cdk/-/cdk-22.1.5.tgz", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-22.1.5.tgz", "integrity": "sha512-1tYAeI5KKIqNGNvyDuMM0Drs2FU3M4B25Q1BlAnwtZe3wvrpVETAGXgHaBX3Hs/pvWfgMNwgbcHkxlKVUBNXMA==", "license": "MIT", "dependencies": { 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 40dd421..cde5cfc 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 @@ -387,17 +387,95 @@ limitations under the License. @if (consolidatedBlocks().length > 0 || showPlanningLoader()) {
- - @if (agentService.activeModel(); as model) { -
+ @if (agentService.viewedModel(); as model) { +
+ + + + + }