` XML block prepended to the prompt.
+
+---
+
+## Expanded E2E Test Coverage (2026-06-09)
+
+> **Scope**: Chat mode (REST API), image attachments, agent web search/browser,
+> code execution, session management, multi-turn context, cross-feature
+> integration, and chat history — beyond the A2A inner loop tests above.
+>
+> **Runner**: `python3 tmp/test_e2e_expanded.py` (supports `TEST_CATEGORY`
+> and `TEST_ID` env-var filters)
+>
+> **Key finding**: A2A inner loop applies to **agent mode only**. Chat mode
+> uses `LLMTurnLoopService` → provider `stream()` directly — no inner loop.
+
+### Expanded Category 1: Infrastructure
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **INF-01** | Backend health | `GET /health` | Returns `{"status":"ok"}` | PASS |
+| **INF-02** | LLM models configured | `GET /v1/user-settings/models` | ≥ 2 models returned | PASS |
+| **INF-03** | Sandbox running | `docker ps --filter name=ii-sandbox` | Container exists or on-demand | PASS |
+
+### Expanded Category 2: Chat Mode (REST API)
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **CHAT-01** | Basic chat — Anthropic | `POST /v1/chat/conversations` with Claude | Response contains expected answer | PASS |
+| **CHAT-02** | Basic chat — OpenAI | Same with GPT-4o | Response contains expected answer | SKIP (quota) |
+| **CHAT-03** | Multi-turn context | 2-turn chat, recall prior info | Turn 2 recalls fact from turn 1 | PASS |
+| **CHAT-04** | Web search tool | Chat with `tools: {web_search: true}` | Substantive response with search results | PASS |
+| **CHAT-05** | Long streaming response | Request 200-word summary | Response > 300 chars, `complete` event | PASS |
+| **CHAT-06** | Stop/interrupt stream | Start long response, short timeout | Content collected or timeout handled | PASS |
+
+### Expanded Category 3: Image Attachments
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **IMG-01** | Image upload flow | `POST /v1/assets/upload` → PUT → `/complete` | Asset ID returned | PASS |
+| **IMG-02** | Chat with image | Chat message with `file_ids` | Response acknowledges image | PASS |
+| **IMG-03** | Agent with image | Socket.IO query with `files` param | Agent completes with image ref | PASS |
+
+### Expanded Category 4: Agent Web Search & Browser
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **WEB-01** | Agent web search | Socket.IO query requesting web search | Agent completes with search results | PASS |
+| **WEB-02** | Agent browser nav | Socket.IO query to navigate example.com | Agent returns page heading "Example Domain" | PASS |
+
+### Expanded Category 5: Code Execution
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **CODE-01** | Create & run script | Agent creates fib.py + executes it | Output shows Fibonacci numbers | PASS |
+| **CODE-02** | Multi-file project | Agent creates utils.py + main.py, runs main | Output contains "15" | PASS |
+
+### Expanded Category 6: Session Management
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **SESS-01** | List sessions | `GET /v1/sessions` | Returns session list | PASS |
+| **SESS-02** | Session events | Create session → `GET /v1/sessions/{id}/events` | Events returned | PASS |
+| **SESS-03** | Pin/unpin session | `POST /v1/sessions/pins/{id}` + `GET /v1/sessions/pins` | Pin created, list returns 200 | PASS |
+| **SESS-04** | Fork session | Create research session → `POST /v1/sessions/{id}/fork` | New session ID returned | PASS |
+
+### Expanded Category 7: Agent Multi-Turn
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **AGEN-01** | Multi-turn context | Turn 1: set fact → Turn 2: recall | Turn 2 recalls fact | PASS |
+| **AGEN-02** | Multi-turn tool use | Turn 1: create file → Turn 2: read file | File content returned correctly | PASS |
+
+### Expanded Category 8: Cross-Feature Integration
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **XFEAT-01** | Web search + file save | Agent searches web, saves to file, reads back | Multiple tool calls, file confirmed | PASS |
+| **XFEAT-02** | Chat vs agent isolation | Chat sets fact in session A, agent in session B | Agent does NOT know chat's fact | PASS |
+
+### Expanded Category 9: Chat History
+
+| ID | Test | Method | Pass Criteria | Status |
+|----|------|--------|---------------|--------|
+| **HIST-01** | Message history | Create chat → `GET /v1/chat/conversations/{id}` | Messages returned with metadata | PASS |
+
+### Expanded Execution Log
+
+| ID | Executed | Result | Notes |
+|----|----------|--------|-------|
+| INF-01 | 2026-06-09 | PASS | `{"status":"ok"}` |
+| INF-02 | 2026-06-09 | PASS | 4 models: gpt-4o, claude-sonnet-4-5, claude-opus-4-6, claude-sonnet-4-6 |
+| INF-03 | 2026-06-09 | PASS | Multiple sandbox containers running |
+| CHAT-01 | 2026-06-09 | PASS | Claude returned "4" for 2+2 |
+| CHAT-02 | 2026-06-09 | SKIP | OpenAI quota exceeded (billing issue — not a code bug) |
+| CHAT-03 | 2026-06-09 | PASS | Neptune recalled across turns |
+| CHAT-04 | 2026-06-09 | PASS | Web search returned Iceland population data |
+| CHAT-05 | 2026-06-09 | PASS | 1369 chars, `complete` event received |
+| CHAT-06 | 2026-06-09 | PASS | 6850 chars collected before timeout |
+| IMG-01 | 2026-06-09 | PASS | Asset upload + complete flow working |
+| IMG-02 | 2026-06-09 | PASS | Chat acknowledged image (note: load error on 1x1 test PNG — cosmetic) |
+| IMG-03 | 2026-06-09 | PASS | Agent completed with image reference |
+| WEB-01 | 2026-06-09 | PASS | Python 3.13.0 release date (Oct 7, 2024) returned |
+| WEB-02 | 2026-06-09 | PASS | "Example Domain" heading correctly identified |
+| CODE-01 | 2026-06-09 | PASS | Fibonacci: 0,1,1,2,3,5,8,13,21,34 |
+| CODE-02 | 2026-06-09 | PASS | Output: 15 |
+| SESS-01 | 2026-06-09 | PASS | 20 sessions listed |
+| SESS-02 | 2026-06-09 | PASS | 5 events for test session |
+| SESS-03 | 2026-06-09 | PASS | Pin created and listed |
+| SESS-04 | 2026-06-09 | PASS | Fork: research session → website session |
+| AGEN-01 | 2026-06-09 | PASS | "Muffin" recalled across agent turns |
+| AGEN-02 | 2026-06-09 | PASS | File created in turn 1, read back "Hello E2E Test" in turn 2 |
+| XFEAT-01 | 2026-06-09 | PASS | Web search + file write + file read — 6 tool calls |
+| XFEAT-02 | 2026-06-09 | PASS | Chat session isolated from agent session (42 not leaked) |
+| HIST-01 | 2026-06-09 | PASS | 2 messages returned with `has_more`, `total_count` metadata |
+
+### Expanded Bug Tracker
+
+| Bug ID | Test ID | Description | Status | Fix |
+|--------|---------|-------------|--------|-----|
+| BUG-002 | CHAT-02 | OpenAI `reasoning.effort` sent unconditionally to non-CoT models (GPT-4o rejects it) | CLOSED | `src/ii_agent/chat/llm/openai.py` lines 884+1019: Changed to conditionally send `reasoning` only when `self.llm_config.cot_model is True`. Both `send()` and `stream()` methods fixed. |
+
+### Features Not Tested (Unconfigured/Unavailable)
+
+| Feature | Reason |
+|---------|--------|
+| OpenAI GPT-4o chat | API quota exceeded (billing) — code fix verified, test marked SKIP |
+| Tool server (port 1236) | Not running in local stack |
+| MCP server (port 6060) | Not running in local stack |
+| Composio integrations | No API keys configured |
+| Apple auth / TestFlight | Destructive, requires Apple credentials |
+| Cloud Run deployment | Destructive, requires GCP project |
+| Audio attachments | No audio generation configured locally |
diff --git a/e2b.Dockerfile b/e2b.Dockerfile
index be04871bf..12fe4283d 100644
--- a/e2b.Dockerfile
+++ b/e2b.Dockerfile
@@ -57,6 +57,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
unzip \
libmagic1 \
xvfb \
+ x11vnc \
+ novnc \
+ websockify \
+ fluxbox \
pandoc \
weasyprint \
libpq-dev \
@@ -82,6 +86,16 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
# Optimization: Combine all curl installs and npm installs into fewer layers
RUN curl -fsSL https://code-server.dev/install.sh | sh
+# GitHub CLI (gh) — required by the Copilot A2A backend (`gh copilot agent`)
+RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
+ -o /usr/share/keyrings/githubcli-archive-keyring.gpg && \
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
+ > /etc/apt/sources.list.d/github-cli.list && \
+ apt-get update && apt-get install -y gh && \
+ rm -rf /var/lib/apt/lists/*
+
# Optimization: Use npm cache mount and install playwright package and system deps as root
RUN --mount=type=cache,target=/root/.npm \
npm install -g agent-browser @intelligent-internet/codex @ast-grep/cli @anthropic-ai/claude-code
@@ -144,6 +158,12 @@ RUN --mount=type=cache,target=/root/.cache/uv \
COPY src/ii_server /app/ii_sandbox/src/ii_server
COPY src/ii_agent_tools /app/ii_sandbox/src/ii_agent_tools
+# Copy the A2A adapter subtree + minimal parent __init__.py files so
+# `python -m ii_agent.integrations.a2a.adapter_server` resolves inside the sandbox.
+COPY src/ii_agent/__init__.py /app/ii_sandbox/src/ii_agent/__init__.py
+COPY src/ii_agent/integrations/__init__.py /app/ii_sandbox/src/ii_agent/integrations/__init__.py
+COPY src/ii_agent/integrations/a2a /app/ii_sandbox/src/ii_agent/integrations/a2a
+
# Optimization: Copy from cached location in codex-builder
COPY --from=codex-builder /sse-http-server /usr/local/bin/sse-http-server
@@ -185,10 +205,21 @@ ENV PATH="/home/user/.bun/bin:/app/ii_sandbox/.venv/bin:$PATH"
USER user
-# Install Playwright browser binaries
+# Install Playwright browser binaries and create system symlinks
RUN playwright install chromium
+USER root
+RUN CHROME_BIN=$(find /home/user/.cache/ms-playwright -name chrome -path '*/chrome-linux/*' | head -1) && \
+ ln -sf "$CHROME_BIN" /usr/local/bin/chromium-browser && \
+ ln -sf "$CHROME_BIN" /usr/local/bin/chromium && \
+ ln -sf "$CHROME_BIN" /usr/local/bin/google-chrome
+USER user
WORKDIR /home/user
+# A2A adapter port — served by ii_agent.integrations.a2a.adapter_server
+# (launched by start-services.sh; default 18100 is in the control-plane range 18000-18999)
+ENV SANDBOX_ADAPTER_PORT=18100
+EXPOSE 18100
+
ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["bash", "/app/start-services.sh"]
diff --git a/frontend/package.json b/frontend/package.json
index cbb3d71a3..8968e730b 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -15,7 +15,9 @@
"tauri": "tauri",
"prepare": "husky",
"lint": "eslint . --report-unused-disable-directives --max-warnings 0",
- "format": "prettier --write ."
+ "format": "prettier --write .",
+ "test": "vitest run",
+ "test:watch": "vitest"
},
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
@@ -128,6 +130,7 @@
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.1",
"vite": "^6.3.4",
- "vite-plugin-svgr": "^4.3.0"
+ "vite-plugin-svgr": "^4.3.0",
+ "vitest": "^3.2.1"
}
}
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index 0bf002b7f..acf4a603b 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -327,6 +327,9 @@ importers:
vite-plugin-svgr:
specifier: ^4.3.0
version: 4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))
+ vitest:
+ specifier: ^3.2.1
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)
packages:
@@ -1315,56 +1318,67 @@ packages:
resolution: {integrity: sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.46.2':
resolution: {integrity: sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.46.2':
resolution: {integrity: sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.46.2':
resolution: {integrity: sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-loongarch64-gnu@4.46.2':
resolution: {integrity: sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.46.2':
resolution: {integrity: sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.46.2':
resolution: {integrity: sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.46.2':
resolution: {integrity: sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==}
cpu: [riscv64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.46.2':
resolution: {integrity: sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.46.2':
resolution: {integrity: sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.46.2':
resolution: {integrity: sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.46.2':
resolution: {integrity: sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==}
@@ -1615,24 +1629,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.1.12':
resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.1.12':
resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.1.12':
resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.1.12':
resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==}
@@ -1704,30 +1722,35 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-arm64-musl@2.7.1':
resolution: {integrity: sha512-/HXY0t4FHkpFzjeYS5c16mlA6z0kzn5uKLWptTLTdFSnYpr8FCnOP4Sdkvm2TDQPF2ERxXtNCd+WR/jQugbGnA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@tauri-apps/cli-linux-riscv64-gnu@2.7.1':
resolution: {integrity: sha512-GeW5lVI2GhhnaYckiDzstG2j2Jwlud5d2XefRGwlOK+C/bVGLT1le8MNPYK8wgRlpeK8fG1WnJJYD6Ke7YQ8bg==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-x64-gnu@2.7.1':
resolution: {integrity: sha512-DprxKQkPxIPYwUgg+cscpv2lcIUhn2nxEPlk0UeaiV9vATxCXyytxr1gLcj3xgjGyNPlM0MlJyYaPy1JmRg1cA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@tauri-apps/cli-linux-x64-musl@2.7.1':
resolution: {integrity: sha512-KLlq3kOK7OUyDR757c0zQjPULpGZpLhNB0lZmZpHXvoOUcqZoCXJHh4dT/mryWZJp5ilrem5l8o9ngrDo0X1AA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@tauri-apps/cli-win32-arm64-msvc@2.7.1':
resolution: {integrity: sha512-dH7KUjKkSypCeWPiainHyXoES3obS+JIZVoSwSZfKq2gWgs48FY3oT0hQNYrWveE+VR4VoR3b/F3CPGbgFvksA==}
@@ -1782,6 +1805,9 @@ packages:
'@types/babel__traverse@7.28.0':
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+ '@types/chai@5.2.3':
+ resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@@ -1878,6 +1904,9 @@ packages:
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
+ '@types/deep-eql@4.0.2':
+ resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+
'@types/estree-jsx@1.0.5':
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
@@ -2013,6 +2042,35 @@ packages:
peerDependencies:
vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+ '@vitest/expect@3.2.4':
+ resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
+
+ '@vitest/mocker@3.2.4':
+ resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==}
+ peerDependencies:
+ msw: ^2.4.9
+ vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0
+ peerDependenciesMeta:
+ msw:
+ optional: true
+ vite:
+ optional: true
+
+ '@vitest/pretty-format@3.2.4':
+ resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==}
+
+ '@vitest/runner@3.2.4':
+ resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==}
+
+ '@vitest/snapshot@3.2.4':
+ resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==}
+
+ '@vitest/spy@3.2.4':
+ resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==}
+
+ '@vitest/utils@3.2.4':
+ resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==}
+
'@xterm/addon-fit@0.10.0':
resolution: {integrity: sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==}
peerDependencies:
@@ -2108,6 +2166,10 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
+ assertion-error@2.0.1:
+ resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
+ engines: {node: '>=12'}
+
async-function@1.0.0:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
@@ -2154,6 +2216,10 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+ cac@6.7.14:
+ resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
+ engines: {node: '>=8'}
+
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -2184,6 +2250,10 @@ packages:
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+ chai@5.3.3:
+ resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
+ engines: {node: '>=18'}
+
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@@ -2204,6 +2274,10 @@ packages:
character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
+ check-error@2.1.3:
+ resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
+ engines: {node: '>= 16'}
+
chevrotain-allstar@0.3.1:
resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==}
peerDependencies:
@@ -2518,6 +2592,10 @@ packages:
decode-named-character-reference@1.2.0:
resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==}
+ deep-eql@5.0.2:
+ resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
+ engines: {node: '>=6'}
+
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -2629,6 +2707,9 @@ packages:
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
engines: {node: '>= 0.4'}
+ es-module-lexer@1.7.0:
+ resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
engines: {node: '>= 0.4'}
@@ -2718,6 +2799,9 @@ packages:
estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
+ estree-walker@3.0.3:
+ resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+
esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
@@ -2733,6 +2817,10 @@ packages:
resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
engines: {node: '>=16.17'}
+ expect-type@1.3.0:
+ resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
+ engines: {node: '>=12.0.0'}
+
exsolve@1.0.7:
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
@@ -3229,6 +3317,9 @@ packages:
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+ js-tokens@9.0.1:
+ resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
+
js-yaml@4.1.0:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
@@ -3327,24 +3418,28 @@ packages:
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-arm64-musl@1.30.1:
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
engines: {node: '>= 12.0.0'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
lightningcss-linux-x64-gnu@1.30.1:
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
lightningcss-linux-x64-musl@1.30.1:
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
engines: {node: '>= 12.0.0'}
cpu: [x64]
os: [linux]
+ libc: [musl]
lightningcss-win32-arm64-msvc@1.30.1:
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
@@ -3415,6 +3510,9 @@ packages:
lottie-web@5.13.0:
resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==}
+ loupe@3.2.1:
+ resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+
lower-case@2.0.2:
resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
@@ -3865,6 +3963,10 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ pathval@2.0.1:
+ resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
+ engines: {node: '>= 14.16'}
+
performance-now@2.1.0:
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
@@ -4278,6 +4380,9 @@ packages:
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
engines: {node: '>= 0.4'}
+ siginfo@2.0.0:
+ resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+
signal-exit@4.1.0:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
@@ -4321,6 +4426,9 @@ packages:
space-separated-tokens@2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
+ stackback@0.0.2:
+ resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+
stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'}
@@ -4328,6 +4436,9 @@ packages:
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
+ std-env@3.10.0:
+ resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -4382,6 +4493,9 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ strip-literal@3.1.0:
+ resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==}
+
style-to-js@1.1.17:
resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==}
@@ -4433,6 +4547,12 @@ packages:
text-segmentation@1.0.3:
resolution: {integrity: sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==}
+ tinybench@2.9.0:
+ resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
tinyexec@1.0.1:
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
@@ -4440,6 +4560,18 @@ packages:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
+ tinypool@1.1.1:
+ resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
+
+ tinyrainbow@2.0.0:
+ resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==}
+ engines: {node: '>=14.0.0'}
+
+ tinyspy@4.0.4:
+ resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==}
+ engines: {node: '>=14.0.0'}
+
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -4604,6 +4736,11 @@ packages:
vfile@6.0.3:
resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+ vite-node@3.2.4:
+ resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+
vite-plugin-svgr@4.3.0:
resolution: {integrity: sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==}
peerDependencies:
@@ -4649,6 +4786,34 @@ packages:
yaml:
optional: true
+ vitest@3.2.4:
+ resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==}
+ engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
+ hasBin: true
+ peerDependencies:
+ '@edge-runtime/vm': '*'
+ '@types/debug': ^4.1.12
+ '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
+ '@vitest/browser': 3.2.4
+ '@vitest/ui': 3.2.4
+ happy-dom: '*'
+ jsdom: '*'
+ peerDependenciesMeta:
+ '@edge-runtime/vm':
+ optional: true
+ '@types/debug':
+ optional: true
+ '@types/node':
+ optional: true
+ '@vitest/browser':
+ optional: true
+ '@vitest/ui':
+ optional: true
+ happy-dom:
+ optional: true
+ jsdom:
+ optional: true
+
void-elements@3.1.0:
resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
engines: {node: '>=0.10.0'}
@@ -4710,6 +4875,11 @@ packages:
engines: {node: '>= 8'}
hasBin: true
+ why-is-node-running@2.3.0:
+ resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==}
+ engines: {node: '>=8'}
+ hasBin: true
+
word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
@@ -6153,6 +6323,11 @@ snapshots:
dependencies:
'@babel/types': 7.28.2
+ '@types/chai@5.2.3':
+ dependencies:
+ '@types/deep-eql': 4.0.2
+ assertion-error: 2.0.1
+
'@types/d3-array@3.2.2': {}
'@types/d3-axis@3.0.6':
@@ -6274,6 +6449,8 @@ snapshots:
dependencies:
'@types/ms': 2.1.0
+ '@types/deep-eql@4.0.2': {}
+
'@types/estree-jsx@1.0.5':
dependencies:
'@types/estree': 1.0.8
@@ -6447,6 +6624,48 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitest/expect@3.2.4':
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ tinyrainbow: 2.0.0
+
+ '@vitest/mocker@3.2.4(vite@6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))':
+ dependencies:
+ '@vitest/spy': 3.2.4
+ estree-walker: 3.0.3
+ magic-string: 0.30.17
+ optionalDependencies:
+ vite: 6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)
+
+ '@vitest/pretty-format@3.2.4':
+ dependencies:
+ tinyrainbow: 2.0.0
+
+ '@vitest/runner@3.2.4':
+ dependencies:
+ '@vitest/utils': 3.2.4
+ pathe: 2.0.3
+ strip-literal: 3.1.0
+
+ '@vitest/snapshot@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ magic-string: 0.30.17
+ pathe: 2.0.3
+
+ '@vitest/spy@3.2.4':
+ dependencies:
+ tinyspy: 4.0.4
+
+ '@vitest/utils@3.2.4':
+ dependencies:
+ '@vitest/pretty-format': 3.2.4
+ loupe: 3.2.1
+ tinyrainbow: 2.0.0
+
'@xterm/addon-fit@0.10.0(@xterm/xterm@5.5.0)':
dependencies:
'@xterm/xterm': 5.5.0
@@ -6583,6 +6802,8 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
+ assertion-error@2.0.1: {}
+
async-function@1.0.0: {}
asynckit@0.4.0: {}
@@ -6630,6 +6851,8 @@ snapshots:
buffer-from@1.1.2: {}
+ cac@6.7.14: {}
+
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -6667,6 +6890,14 @@ snapshots:
ccount@2.0.1: {}
+ chai@5.3.3:
+ dependencies:
+ assertion-error: 2.0.1
+ check-error: 2.1.3
+ deep-eql: 5.0.2
+ loupe: 3.2.1
+ pathval: 2.0.1
+
chalk@4.1.2:
dependencies:
ansi-styles: 4.3.0
@@ -6682,6 +6913,8 @@ snapshots:
character-reference-invalid@2.0.1: {}
+ check-error@2.1.3: {}
+
chevrotain-allstar@0.3.1(chevrotain@11.0.3):
dependencies:
chevrotain: 11.0.3
@@ -7024,6 +7257,8 @@ snapshots:
dependencies:
character-entities: 2.0.2
+ deep-eql@5.0.2: {}
+
deep-is@0.1.4: {}
define-data-property@1.1.4:
@@ -7200,6 +7435,8 @@ snapshots:
iterator.prototype: 1.1.5
safe-array-concat: 1.1.3
+ es-module-lexer@1.7.0: {}
+
es-object-atoms@1.1.1:
dependencies:
es-errors: 1.3.0
@@ -7353,6 +7590,10 @@ snapshots:
estree-walker@2.0.2: {}
+ estree-walker@3.0.3:
+ dependencies:
+ '@types/estree': 1.0.8
+
esutils@2.0.3: {}
eventemitter3@5.0.1: {}
@@ -7371,6 +7612,8 @@ snapshots:
signal-exit: 4.1.0
strip-final-newline: 3.0.0
+ expect-type@1.3.0: {}
+
exsolve@1.0.7: {}
extend@3.0.2: {}
@@ -7908,6 +8151,8 @@ snapshots:
js-tokens@4.0.0: {}
+ js-tokens@9.0.1: {}
+
js-yaml@4.1.0:
dependencies:
argparse: 2.0.1
@@ -8095,6 +8340,8 @@ snapshots:
lottie-web@5.13.0: {}
+ loupe@3.2.1: {}
+
lower-case@2.0.2:
dependencies:
tslib: 2.8.1
@@ -8781,6 +9028,8 @@ snapshots:
pathe@2.0.3: {}
+ pathval@2.0.1: {}
+
performance-now@2.1.0:
optional: true
@@ -9276,6 +9525,8 @@ snapshots:
side-channel-map: 1.0.1
side-channel-weakmap: 1.0.2
+ siginfo@2.0.0: {}
+
signal-exit@4.1.0: {}
slice-ansi@5.0.0:
@@ -9327,11 +9578,15 @@ snapshots:
space-separated-tokens@2.0.2: {}
+ stackback@0.0.2: {}
+
stackblur-canvas@2.7.0:
optional: true
state-local@1.0.7: {}
+ std-env@3.10.0: {}
+
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -9432,6 +9687,10 @@ snapshots:
strip-json-comments@3.1.1: {}
+ strip-literal@3.1.0:
+ dependencies:
+ js-tokens: 9.0.1
+
style-to-js@1.1.17:
dependencies:
style-to-object: 1.0.9
@@ -9484,6 +9743,10 @@ snapshots:
utrie: 1.0.2
optional: true
+ tinybench@2.9.0: {}
+
+ tinyexec@0.3.2: {}
+
tinyexec@1.0.1: {}
tinyglobby@0.2.14:
@@ -9491,6 +9754,12 @@ snapshots:
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
+ tinypool@1.1.1: {}
+
+ tinyrainbow@2.0.0: {}
+
+ tinyspy@4.0.4: {}
+
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -9690,6 +9959,27 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
+ vite-node@3.2.4(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.4.1
+ es-module-lexer: 1.7.0
+ pathe: 2.0.3
+ vite: 6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)
+ transitivePeerDependencies:
+ - '@types/node'
+ - jiti
+ - less
+ - lightningcss
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
vite-plugin-svgr@4.3.0(rollup@4.46.2)(typescript@5.9.2)(vite@6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)):
dependencies:
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
@@ -9717,6 +10007,48 @@ snapshots:
terser: 5.43.1
yaml: 2.8.1
+ vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1):
+ dependencies:
+ '@types/chai': 5.2.3
+ '@vitest/expect': 3.2.4
+ '@vitest/mocker': 3.2.4(vite@6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1))
+ '@vitest/pretty-format': 3.2.4
+ '@vitest/runner': 3.2.4
+ '@vitest/snapshot': 3.2.4
+ '@vitest/spy': 3.2.4
+ '@vitest/utils': 3.2.4
+ chai: 5.3.3
+ debug: 4.4.1
+ expect-type: 1.3.0
+ magic-string: 0.30.17
+ pathe: 2.0.3
+ picomatch: 4.0.3
+ std-env: 3.10.0
+ tinybench: 2.9.0
+ tinyexec: 0.3.2
+ tinyglobby: 0.2.14
+ tinypool: 1.1.1
+ tinyrainbow: 2.0.0
+ vite: 6.3.5(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@22.17.2)(jiti@2.5.1)(lightningcss@1.30.1)(terser@5.43.1)(yaml@2.8.1)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/debug': 4.1.12
+ '@types/node': 22.17.2
+ transitivePeerDependencies:
+ - jiti
+ - less
+ - lightningcss
+ - msw
+ - sass
+ - sass-embedded
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+ - tsx
+ - yaml
+
void-elements@3.1.0: {}
vscode-jsonrpc@8.2.0: {}
@@ -9794,6 +10126,11 @@ snapshots:
dependencies:
isexe: 2.0.0
+ why-is-node-running@2.3.0:
+ dependencies:
+ siginfo: 2.0.0
+ stackback: 0.0.2
+
word-wrap@1.2.5: {}
wrap-ansi@9.0.0:
diff --git a/frontend/src/app/routes/agent.tsx b/frontend/src/app/routes/agent.tsx
index cc236a2e2..a5caf7c34 100644
--- a/frontend/src/app/routes/agent.tsx
+++ b/frontend/src/app/routes/agent.tsx
@@ -13,6 +13,7 @@ import AgentTasks from '@/components/agent/agent-task'
import ChatBox from '@/components/agent/chat-box'
import AgentHeader from '@/components/header'
import RightSidebar from '@/components/right-sidebar'
+import { rewriteLocalhostUrl } from '@/lib/utils'
import { sessionService } from '@/services/session.service'
import {
selectActiveTab,
@@ -91,7 +92,7 @@ function AgentPageContent() {
)
// PiP preview URL (mobile takes priority over fullstack)
- const pipUrl = mobileWebPreviewUrl || previewUrl
+ const pipUrl = rewriteLocalhostUrl(mobileWebPreviewUrl || previewUrl)
const showPiP =
!isMobile &&
activeTab !== TAB.RESULT &&
@@ -160,6 +161,11 @@ function AgentPageContent() {
fetchSession()
}, 5000)
} else {
+ // Redirect chat sessions to the chat page
+ if (data.agent_type === 'chat') {
+ navigate(`/chat?id=${sessionId}`, { replace: true })
+ return
+ }
dispatch(setSelectedFeature(data.agent_type ?? null))
dispatch(setProjectId(data.project_id ?? null))
setSessionData(data)
diff --git a/frontend/src/app/routes/dashboard.tsx b/frontend/src/app/routes/dashboard.tsx
index 01cefd65a..4901a122b 100644
--- a/frontend/src/app/routes/dashboard.tsx
+++ b/frontend/src/app/routes/dashboard.tsx
@@ -45,9 +45,11 @@ import {
import { wishlistService } from '@/services/wishlist.service'
import { sessionService } from '@/services/session.service'
import { ISession } from '@/typings/agent'
-import { deleteSession } from '@/state/slice/sessions'
+import { deleteSession, selectActiveSessionId } from '@/state/slice/sessions'
import { clearSessionState } from '@/state/slice/session-state'
import { removePin } from '@/state/slice/pins'
+import { setRunStatus } from '@/state/slice/agent'
+import { setLoading } from '@/state'
enum TAB {
ALL = 'all',
@@ -74,6 +76,7 @@ export function DashboardPage() {
const currentPage = useAppSelector(selectSessionsPage)
const limit = useAppSelector(selectSessionsLimit)
const favoriteSessionIds = useAppSelector(selectFavoriteSessionIds)
+ const activeSessionId = useAppSelector(selectActiveSessionId)
const handleBack = () => {
navigate(-1)
@@ -117,6 +120,10 @@ export function DashboardPage() {
await dispatch(deleteSession(deleteSessionId)).unwrap()
dispatch(clearSessionState(deleteSessionId))
dispatch(removePin(deleteSessionId))
+ if (deleteSessionId === activeSessionId) {
+ dispatch(setRunStatus(null))
+ dispatch(setLoading(false))
+ }
setIsDeleteDialogOpen(false)
setDeleteSessionId(null)
} catch (error) {
diff --git a/frontend/src/app/routes/login.tsx b/frontend/src/app/routes/login.tsx
index 8b278afef..427ad861a 100644
--- a/frontend/src/app/routes/login.tsx
+++ b/frontend/src/app/routes/login.tsx
@@ -1,5 +1,5 @@
import { useGoogleLogin } from '@react-oauth/google'
-import { useCallback, useEffect, useMemo, useRef } from 'react'
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Link, useNavigate } from 'react-router'
import { useForm } from 'react-hook-form'
import { z } from 'zod'
@@ -344,6 +344,10 @@ export function LoginPage() {
/>
{t('auth.continueWithII')}
+
{t('auth.privacyNotice')}{' '}
@@ -359,4 +363,53 @@ export function LoginPage() {
)
}
+/**
+ * Dev login button - only shows if DEV_AUTH_ENABLED is set on backend
+ */
+function DevLoginButton({
+ apiBaseUrl,
+ onSuccess
+}: {
+ apiBaseUrl: string
+ onSuccess: (payload: IiAuthPayload | null | undefined) => Promise
+}) {
+ const [isAvailable, setIsAvailable] = useState(null)
+
+ useEffect(() => {
+ // Check if dev login is available
+ fetch(`${apiBaseUrl}/auth/dev/login`)
+ .then((res) => {
+ setIsAvailable(res.ok)
+ })
+ .catch(() => setIsAvailable(false))
+ }, [apiBaseUrl])
+
+ const handleDevLogin = async () => {
+ try {
+ const res = await fetch(`${apiBaseUrl}/auth/dev/login`)
+ if (!res.ok) {
+ throw new Error('Dev login failed')
+ }
+ const data = await res.json()
+ await onSuccess(data)
+ } catch (error) {
+ console.error('Dev login failed:', error)
+ }
+ }
+
+ if (isAvailable !== true) {
+ return null
+ }
+
+ return (
+
+ )
+}
+
export const Component = LoginPage
diff --git a/frontend/src/components/agent/agent-result.tsx b/frontend/src/components/agent/agent-result.tsx
index 55317f22b..6549281cd 100644
--- a/frontend/src/components/agent/agent-result.tsx
+++ b/frontend/src/components/agent/agent-result.tsx
@@ -7,6 +7,7 @@ import {
selectIsLoading,
selectIsSandboxIframeAwake,
selectMessages,
+ selectSandboxStatus,
useAppSelector
} from '@/state'
import { CommandType, TAB, TOOL } from '@/typings/agent'
@@ -15,7 +16,7 @@ import MobileResult from './mobile-result'
import { Icon } from '../ui/icon'
import AwakeMeUpScreen from './awake-me-up-screen'
import { useLocation, useParams } from 'react-router'
-import { cn, isE2bLink } from '@/lib/utils'
+import { cn, isSandboxLink, rewriteLocalhostUrl } from '@/lib/utils'
import { DesignModeWrapper } from '@/components/design-mode'
import { useTranslation } from 'react-i18next'
import {
@@ -45,6 +46,7 @@ const AgentResult = ({ className }: AgentResultProps) => {
const activeTab = useAppSelector(selectActiveTab)
const isSandboxIframeAwake = useAppSelector(selectIsSandboxIframeAwake)
+ const sandboxStatus = useAppSelector(selectSandboxStatus)
const messages = useAppSelector(selectMessages)
const isRunning = useAppSelector(selectIsLoading)
const isShareMode = useMemo(
@@ -89,7 +91,7 @@ const AgentResult = ({ className }: AgentResultProps) => {
mobileAppResult as { web_preview_url?: string }
).web_preview_url
if (webPreviewUrl) {
- return webPreviewUrl
+ return rewriteLocalhostUrl(webPreviewUrl)
}
}
@@ -106,7 +108,7 @@ const AgentResult = ({ className }: AgentResultProps) => {
if (result && typeof result === 'object') {
const previewUrl = (result as { preview_url?: string }).preview_url
if (previewUrl) {
- return previewUrl
+ return rewriteLocalhostUrl(previewUrl)
}
}
return ''
@@ -256,12 +258,12 @@ const AgentResult = ({ className }: AgentResultProps) => {
const shouldShowAwakeScreen = useMemo(() => {
return (
- isE2bLink(resultUrl) &&
+ sandboxStatus === 'paused' &&
!isSandboxIframeAwake &&
!isRunning &&
!isShareMode
)
- }, [resultUrl, isSandboxIframeAwake, isRunning, isShareMode])
+ }, [sandboxStatus, isSandboxIframeAwake, isRunning, isShareMode])
// Extract slide data from SlideWrite and SlideEdit messages
const slideContent = useMemo(() => {
@@ -323,7 +325,7 @@ const AgentResult = ({ className }: AgentResultProps) => {
// Check if design mode should be available (only for e2b sandbox websites)
const isDesignModeAvailable = useMemo(() => {
if (!resultUrl) return false
- if (!isE2bLink(resultUrl)) return false
+ if (!isSandboxLink(resultUrl)) return false
if (detectUrlType(resultUrl) !== 'website') return false
if (isShareMode) return false
return true
@@ -338,8 +340,6 @@ const AgentResult = ({ className }: AgentResultProps) => {
)
}
- if (!resultUrl && !mobileAppUrl) return null
-
if (shouldShowAwakeScreen)
return (
{
/>
)
+ if (!resultUrl && !mobileAppUrl) return null
+
if (hasMobileAppTools && activeTab === TAB.RESULT) {
return (
{
const activeTab = useAppSelector(selectActiveTab)
const vscodeUrl = useAppSelector(selectVscodeUrl)
+ const vncUrl = useAppSelector(selectVncUrl)
const isShareMode = useMemo(
() => location.pathname.includes('/share/'),
@@ -44,6 +46,15 @@ const AgentTabs = ({ sessionId, projectId, agentType }: AgentTabsProps) => {
window.open(vscodeUrl, '_blank')
}
+ const handleOpenVNC = () => {
+ if (!vncUrl) {
+ toast.error(t('agentTab.errors.vncUrlMissing', 'noVNC URL not available'))
+ return
+ }
+
+ window.open(vncUrl, '_blank')
+ }
+
const shouldShowProjectTab = useMemo(() => {
if (isShareMode) {
return false
@@ -114,6 +125,15 @@ const AgentTabs = ({ sessionId, projectId, agentType }: AgentTabsProps) => {
{t('agentTab.openInVSCode')}
)}
+ {vncUrl && !isShareMode && (
+
+ )}
{agentType === AGENT_TYPE.MOBILE_APP ? (
{
const { t } = useTranslation()
const messages = useAppSelector(selectMessages)
+ const isStopped = useAppSelector(selectIsStopped)
const dispatch = useAppDispatch()
const [plans, setPlans] = useState([])
@@ -28,6 +29,9 @@ const AgentTasks = ({ className }: AgentTasksProps) => {
}, [messages])
useEffect(() => {
+ // Don't auto-promote tasks if the agent is stopped
+ if (isStopped) return
+
if (Array.isArray(plans)) {
// Check if there are no in_progress tasks
const hasInProgress = plans.some(
@@ -50,11 +54,11 @@ const AgentTasks = ({ className }: AgentTasksProps) => {
}
}
}
- }, [plans, dispatch])
+ }, [plans, dispatch, isStopped])
const inProgressPlans = useMemo(
- () => countBy(plans, 'status').in_progress || 0,
- [plans]
+ () => isStopped ? 0 : (countBy(plans, 'status').in_progress || 0),
+ [plans, isStopped]
)
const completedPlans = useMemo(
@@ -69,7 +73,7 @@ const AgentTasks = ({ className }: AgentTasksProps) => {
className={`flex flex-col items-center justify-center w-full ${className}`}
>
- {t('agent.tasks.inProgress')}
+ {isStopped ? t('agent.tasks.stopped', 'Stopped') : t('agent.tasks.inProgress')}
diff --git a/frontend/src/components/agent/subagent-container.tsx b/frontend/src/components/agent/subagent-container.tsx
index f88149ba2..27f107240 100644
--- a/frontend/src/components/agent/subagent-container.tsx
+++ b/frontend/src/components/agent/subagent-container.tsx
@@ -7,12 +7,14 @@ import {
CheckCircle2,
XCircle,
Loader2,
- Clock
+ Clock,
+ StopCircle
} from 'lucide-react'
import { useState, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { AgentContext, Message } from '@/typings/agent'
import { formatDuration } from '@/lib/utils'
+import { useAppSelector, selectIsStopped, selectIsLoading } from '@/state'
interface SubagentContainerProps {
agentContext: AgentContext
@@ -23,7 +25,8 @@ interface SubagentContainerProps {
enum SubAgentStatus {
RUNNING = 'running',
COMPLETED = 'completed',
- FAILED = 'failed'
+ FAILED = 'failed',
+ STOPPED = 'stopped'
}
const SubagentContainer = ({
@@ -33,6 +36,8 @@ const SubagentContainer = ({
}: SubagentContainerProps) => {
const { t } = useTranslation()
const [isExpanded, setIsExpanded] = useState(true)
+ const isStopped = useAppSelector(selectIsStopped)
+ const isLoading = useAppSelector(selectIsLoading)
// Calculate execution time
const executionTime = useMemo(() => {
@@ -51,6 +56,7 @@ const SubagentContainer = ({
}, [messages])
// Determine actual status - explicit failed status takes precedence over endTime
+ // Also check global isStopped/isLoading state to determine subagent status
const actualStatus = useMemo(() => {
if (agentContext.status === SubAgentStatus.FAILED) {
return SubAgentStatus.FAILED
@@ -58,14 +64,25 @@ const SubagentContainer = ({
if (agentContext.endTime) {
return SubAgentStatus.COMPLETED
}
- return agentContext.status || SubAgentStatus.RUNNING
- }, [agentContext.status, agentContext.endTime])
+ const contextStatus = agentContext.status || SubAgentStatus.RUNNING
+ // If global agent is stopped and this subagent was still running, show as stopped
+ if (isStopped && contextStatus === SubAgentStatus.RUNNING) {
+ return SubAgentStatus.STOPPED
+ }
+ // If main agent is done (not loading, not stopped) and subagent is still "running",
+ // it means the subagent completed but wasn't marked - show as completed
+ if (!isLoading && !isStopped && contextStatus === SubAgentStatus.RUNNING) {
+ return SubAgentStatus.COMPLETED
+ }
+ return contextStatus
+ }, [agentContext.status, agentContext.endTime, isStopped, isLoading])
const statusLabel = useMemo(() => {
const keyMap: Record
= {
[SubAgentStatus.RUNNING]: 'agent.subagent.status.running',
[SubAgentStatus.COMPLETED]: 'agent.subagent.status.completed',
- [SubAgentStatus.FAILED]: 'agent.subagent.status.failed'
+ [SubAgentStatus.FAILED]: 'agent.subagent.status.failed',
+ [SubAgentStatus.STOPPED]: 'agent.subagent.status.stopped'
}
return t(keyMap[actualStatus] || 'agent.subagent.status.running')
}, [actualStatus, t])
@@ -77,6 +94,8 @@ const SubagentContainer = ({
return
case SubAgentStatus.FAILED:
return
+ case SubAgentStatus.STOPPED:
+ return
case SubAgentStatus.RUNNING:
return
default:
@@ -152,6 +171,7 @@ const SubagentContainer = ({
${actualStatus === SubAgentStatus.COMPLETED ? 'bg-green-500/20 text-green-400' : ''}
${actualStatus === SubAgentStatus.RUNNING ? 'bg-blue-500/20 text-blue-400' : ''}
${actualStatus === SubAgentStatus.FAILED ? 'bg-red-500/20 text-red-400' : ''}
+ ${actualStatus === SubAgentStatus.STOPPED ? 'bg-yellow-500/20 text-yellow-400' : ''}
`}
>
{statusLabel}
diff --git a/frontend/src/components/chat-header-mobile.tsx b/frontend/src/components/chat-header-mobile.tsx
index 27aff14cc..2cf4ce074 100644
--- a/frontend/src/components/chat-header-mobile.tsx
+++ b/frontend/src/components/chat-header-mobile.tsx
@@ -14,6 +14,7 @@ import {
} from '@/state'
import { deleteSession } from '@/state/slice/sessions'
import { clearSessionState } from '@/state/slice/session-state'
+import { setRunStatus } from '@/state/slice/agent'
import { type ISession } from '@/typings/agent'
import HeaderDropdownMenu from '@/components/header-dropdown-menu'
import ShareConversation from '@/components/agent/share-conversation'
@@ -74,6 +75,7 @@ const ChatHeaderMobile = ({
try {
await dispatch(deleteSession(sessionId)).unwrap()
dispatch(clearSessionState(sessionId))
+ dispatch(setRunStatus(null))
setIsDeleteDialogOpen(false)
navigate('/')
} catch (error) {
diff --git a/frontend/src/components/chat-header.tsx b/frontend/src/components/chat-header.tsx
index 921b2c581..9abac8bbe 100644
--- a/frontend/src/components/chat-header.tsx
+++ b/frontend/src/components/chat-header.tsx
@@ -28,6 +28,7 @@ import { useSearchParams } from 'react-router'
import { useNavigate } from 'react-router'
import { deleteSession } from '@/state/slice/sessions'
import { clearSessionState } from '@/state/slice/session-state'
+import { setRunStatus } from '@/state/slice/agent'
import ShareConversation from '@/components/agent/share-conversation'
import {
AlertDialog,
@@ -126,6 +127,10 @@ const ChatHeader = ({
try {
await dispatch(deleteSession(sessionId)).unwrap()
dispatch(clearSessionState(sessionId))
+ resetSessionState()
+ resetConversationState()
+ setSessionId(null)
+ dispatch(setRunStatus(null))
setIsDeleteDialogOpen(false)
navigate('/')
} catch (error) {
diff --git a/frontend/src/components/header.tsx b/frontend/src/components/header.tsx
index ec9b3e736..00396c0d8 100644
--- a/frontend/src/components/header.tsx
+++ b/frontend/src/components/header.tsx
@@ -20,6 +20,7 @@ import {
} from '@/state'
import { deleteSession } from '@/state/slice/sessions'
import { clearSessionState } from '@/state/slice/session-state'
+import { setRunStatus } from '@/state/slice/agent'
import { ISession } from '@/typings'
import {
AlertDialog,
@@ -90,6 +91,7 @@ const AgentHeader = ({ sessionData, isChatPage }: AgentHeaderProps) => {
await dispatch(deleteSession(sessionId)).unwrap()
// Clear cached session state to free up localStorage
dispatch(clearSessionState(sessionId))
+ dispatch(setRunStatus(null))
setIsDeleteDialogOpen(false)
// Navigate to home page after deletion
navigate('/')
diff --git a/frontend/src/components/project-list.tsx b/frontend/src/components/project-list.tsx
index 6464211fc..d5afc292e 100644
--- a/frontend/src/components/project-list.tsx
+++ b/frontend/src/components/project-list.tsx
@@ -45,6 +45,9 @@ import { hasSessionDisplayTitle } from '@/utils/session-title'
interface ProjectListProps {
workspaceInfo?: string
isLoading: boolean
+ loadingMore: boolean
+ hasMore: boolean
+ onLoadMore: () => void
handleResetState: () => void
handleNewProject: () => void
}
@@ -52,6 +55,9 @@ interface ProjectListProps {
const ProjectList = ({
workspaceInfo,
isLoading,
+ loadingMore,
+ hasMore,
+ onLoadMore,
handleResetState,
handleNewProject
}: ProjectListProps) => {
@@ -322,6 +328,25 @@ const ProjectList = ({
{t('sidebar.seeMore')}
)}
+ {loadingMore && (
+
+ {t('common.loadingMore')}
+
+ )}
+ {!loadingMore && hasMore && showAllProjects && (
+
+ )}
{
e.preventDefault()
e.stopPropagation()
+ setIsDropdownOpen(false)
setIsDeleteDialogOpen(true)
}
@@ -105,6 +106,10 @@ const SessionItem = ({
await dispatch(deleteSession(session.id)).unwrap()
dispatch(clearSessionState(session.id))
dispatch(removePin(session.id))
+ if (isActive) {
+ dispatch(setRunStatus(null))
+ dispatch(setLoading(false))
+ }
setIsDeleteDialogOpen(false)
} catch (error) {
console.error('Failed to delete session:', error)
diff --git a/frontend/src/components/share-agent-content.tsx b/frontend/src/components/share-agent-content.tsx
index b36a59d5d..e872bac26 100644
--- a/frontend/src/components/share-agent-content.tsx
+++ b/frontend/src/components/share-agent-content.tsx
@@ -28,7 +28,7 @@ import {
import { BUILD_STEP, ISession, TAB } from '@/typings/agent'
import AgentResult from '@/components/agent/agent-result'
import AgentPopoverDone from '@/components/agent/agent-popover-done'
-import { isE2bLink } from '@/lib/utils'
+import { isSandboxLink } from '@/lib/utils'
import { SidebarProvider } from '@/components/ui/sidebar'
import AgentTabMobile, {
type ChatOption as MobileChatOption
@@ -76,7 +76,9 @@ export function ShareAgentContent() {
fetchSession()
}, 5000)
} else {
- dispatch(setSelectedFeature(data.agent_type ?? null))
+ // Normalize chat sessions to 'general' to prevent invalid agent_type
+ const agentType = data.agent_type === 'chat' ? 'general' : (data.agent_type ?? null)
+ dispatch(setSelectedFeature(agentType))
setSessionData(data)
setSessionError(null) // Clear any previous errors
}
@@ -234,7 +236,7 @@ export function ShareAgentContent() {
- {vscodeUrl && isE2bLink(vscodeUrl) && (
+ {vscodeUrl && isSandboxLink(vscodeUrl) && (