diff --git a/.github/scripts/ci/classify-platform-sensitivity.test.mjs b/.github/scripts/ci/classify-platform-sensitivity.test.mjs index 9bd2f761368..46c0d57f5df 100644 --- a/.github/scripts/ci/classify-platform-sensitivity.test.mjs +++ b/.github/scripts/ci/classify-platform-sensitivity.test.mjs @@ -79,7 +79,7 @@ test('the manifests change what each lane executes', () => { // A workspace manifest is not the root one; it reaches the lanes through // the subsystem rules or not at all. assert.equal( - classifyChangedFiles(['packages/webui/package.json']), + classifyChangedFiles(['packages/web-shell/package.json']), PLATFORM_INSENSITIVE, ); }); @@ -112,7 +112,6 @@ test('platform-coupled subsystems match on segments, not substrings', () => { // The substring trap: these contain "shell", "pty", "os" or "platform" // inside a longer word and must NOT drag both lanes in. for (const file of [ - 'packages/webui/src/components/Shellfish.tsx', 'packages/core/src/utils/cryptic.ts', 'packages/cli/src/ui/emptyState.ts', 'packages/core/src/telemetry/uploader.ts', diff --git a/.github/scripts/web-shell-visuals-publish.mjs b/.github/scripts/web-shell-visuals-publish.mjs index 34ff0e2b66d..3f8d1d5026d 100644 --- a/.github/scripts/web-shell-visuals-publish.mjs +++ b/.github/scripts/web-shell-visuals-publish.mjs @@ -122,10 +122,7 @@ export function selectImages(candidates, opts = {}) { * whether we render at all; this decides whether a "nothing changed" RESULT * deserves a second look. */ -const RENDER_SHAPING_PREFIXES = [ - 'packages/web-shell/client/', - 'packages/webui/src/', -]; +const RENDER_SHAPING_PREFIXES = ['packages/web-shell/client/']; /** * Extensions that change what a view LOOKS like. Deliberately narrow: a `.ts` diff --git a/.github/scripts/web-shell-visuals-publish.test.mjs b/.github/scripts/web-shell-visuals-publish.test.mjs index 302e9ced935..bb7aa8488a2 100644 --- a/.github/scripts/web-shell-visuals-publish.test.mjs +++ b/.github/scripts/web-shell-visuals-publish.test.mjs @@ -783,7 +783,6 @@ test('selectRenderShapingFiles keeps rendered .tsx/.css/.svg and drops logic/tes const { files, total } = selectRenderShapingFiles([ 'packages/web-shell/client/components/WelcomeScreen.tsx', 'packages/web-shell/client/components/worktree.module.css', - 'packages/webui/src/ui/button.tsx', 'packages/web-shell/client/assets/icons/plan.svg', // Dropped: not a rendered extension... 'packages/web-shell/client/hooks/useWorktree.ts', @@ -804,9 +803,8 @@ test('selectRenderShapingFiles keeps rendered .tsx/.css/.svg and drops logic/tes 'packages/web-shell/client/assets/icons/plan.svg', 'packages/web-shell/client/components/WelcomeScreen.tsx', 'packages/web-shell/client/components/worktree.module.css', - 'packages/webui/src/ui/button.tsx', ]); - assert.equal(total, 4); + assert.equal(total, 3); }); test('selectRenderShapingFiles caps the listed paths but reports the true total', () => { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f98f81133d1..cb36268de69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1122,6 +1122,10 @@ jobs: if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: 'npm run check:lockfile' + - name: 'Check retired WebUI dependency' + if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" + run: 'npm run check:no-webui' + - name: 'Check desktop workspace isolation' if: "${{ needs.classify_pr.outputs.skip_ci != 'true' && steps.ci_profile.outputs.ci_profile == 'full' }}" run: 'npm run check:desktop-isolation' diff --git a/.github/workflows/qwen-triage.yml b/.github/workflows/qwen-triage.yml index 73a7114a4fc..cccc7a3c5f6 100644 --- a/.github/workflows/qwen-triage.yml +++ b/.github/workflows/qwen-triage.yml @@ -3547,7 +3547,7 @@ jobs: has_vitest_config() { # Mirror vitest's own resolution: it accepts vitest.config AND # vite.config in six extensions. A narrower probe skips runnable - # packages (packages/webui's only config is vite.config.ts). + # packages (some packages only have vite.config.ts). local n e for n in vitest.config vite.config; do for e in ts mts cts js mjs cjs; do diff --git a/.github/workflows/release-vscode-companion.yml b/.github/workflows/release-vscode-companion.yml index 7ea43856830..4ae6a6ff521 100644 --- a/.github/workflows/release-vscode-companion.yml +++ b/.github/workflows/release-vscode-companion.yml @@ -100,6 +100,16 @@ jobs: run: |- npm ci + - name: 'Verify published export renderer' + run: |- + renderer_version=$(node -p "require('./package.json').version") + published_renderer=$(mktemp) + trap 'rm -f "${published_renderer}"' EXIT + curl --fail --location --silent --show-error --retry 3 \ + "https://unpkg.com/@qwen-code/qwen-code@${renderer_version}/export-transcript-document.js" \ + --output "${published_renderer}" + cmp packages/web-templates/src/export-html/dist/export-transcript-document.js "${published_renderer}" + - name: 'Get the version' id: 'version' working-directory: 'packages/vscode-ide-companion' @@ -152,12 +162,6 @@ jobs: IS_PREVIEW: '${{ steps.vars.outputs.is_preview }}' MANUAL_VERSION: '${{ inputs.version }}' - - name: 'Build webui dependency' - if: |- - ${{ github.event.inputs.force_skip_tests != 'true' }} - run: | - npm run build --workspace=@qwen-code/webui - - name: 'Run Tests' if: |- ${{ github.event.inputs.force_skip_tests != 'true' }} @@ -231,6 +235,8 @@ jobs: RELEASE_VERSION: '${{ needs.prepare.outputs.release_version }}' shell: 'bash' run: |- + # Keep the verified, prebuilt export renderer reference pinned to the + # published CLI version from the selected source ref. npm run release:version -- "${RELEASE_VERSION}" - name: 'Prepare VSCode Extension' diff --git a/.github/workflows/web-shell-visuals.yml b/.github/workflows/web-shell-visuals.yml index 2ed48a24405..1c9b7790f08 100644 --- a/.github/workflows/web-shell-visuals.yml +++ b/.github/workflows/web-shell-visuals.yml @@ -25,11 +25,6 @@ on: - 'packages/web-shell/package.json' - 'packages/web-shell/vite.config.ts' - 'packages/web-shell/playwright.visuals.config.ts' - # The visuals dev server aliases the shared web UI library into the - # rendered bundle (see the `resolve.alias` block in - # packages/web-shell/vite.config.ts), so a change to its components/hooks - # must also refresh the preview. - - 'packages/webui/src/**' # NOTE: packages/sdk-typescript/src/** is deliberately NOT a trigger. # It is aliased in too, but the visuals render against a *mock* daemon, so # the SDK's transport/client layer is stubbed at the network boundary and diff --git a/.qwen/skills/find-simplifications/SKILL.md b/.qwen/skills/find-simplifications/SKILL.md index 7ee5987f684..cc481a5aca3 100644 --- a/.qwen/skills/find-simplifications/SKILL.md +++ b/.qwen/skills/find-simplifications/SKILL.md @@ -61,9 +61,8 @@ this repo**. The release workflow npm-publishes `@qwen-code/audio-capture` and the eight `@qwen-code/channel-*` packages with `--access public`, so a symbol re-exported by their package entry is reachable the same way. No grep inside this repo can prove such a symbol has no consumer. The same is true -of three surfaces whose consumers are not imports at all: `packages/webui` is -npm-published under its own name (`publishConfig.access: public`, no -`private`), and `packages/core/vendor/**` and `packages/web-shell` ship inside +of two surfaces whose consumers are not imports at all: +`packages/core/vendor/**` and `packages/web-shell` ship inside the published `@qwen-code/qwen-code` tarball — `packages/core/package.json` lists `vendor` in `files`, and `scripts/copy_bundle_assets.js` copies both `vendor/` and `web-shell/dist` into the bundle, where `qwen serve` hands the @@ -83,14 +82,13 @@ Landable. | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `packages/cli/src` — the whole package (`generated/` stays under the Never-a-target row below; `**/*.sb` stays under the Report-only row below; `i18n/locales/**` and `commands/extensions/examples/**` stay under the Report-only row below; `**/*.test.ts(x)`, `**/*.spec.ts(x)`, `**/__snapshots__/**` are never targets, always searched as consumers) | Landable | | `scripts/`, `esbuild.config.js`, `eslint.legacy-filenames.mjs`, root manifests | Landable | -| Whole files or directories nothing consumes by any mechanism named above — no import and no runtime read, loader, manifest, or tool config — anywhere outside `packages/core/src`, `packages/audio-capture`, `packages/channels`, `packages/sdk-*`, `packages/acp-bridge`, `packages/vscode-ide-companion`, `packages/chrome-extension`, `packages/zed-extension`, `packages/webui`, `packages/web-shell`, `packages/core/vendor`, `.github`, and the Never-a-target row below | Landable | +| Whole files or directories nothing consumes by any mechanism named above — no import and no runtime read, loader, manifest, or tool config — anywhere outside `packages/core/src`, `packages/audio-capture`, `packages/channels`, `packages/sdk-*`, `packages/acp-bridge`, `packages/vscode-ide-companion`, `packages/chrome-extension`, `packages/zed-extension`, `packages/web-shell`, `packages/core/vendor`, `.github`, and the Never-a-target row below | Landable | | `docs/users/**`, `docs/developers/**`, `docs/index.md`, `docs/_meta.ts`, `packages/cli/src/i18n/locales/**`, `packages/cli/src/commands/extensions/examples/**` — as whole files or directories | **Report-only** — copied into the published tarball (`scripts/prepare-package.js` copies the locales and extension examples; `scripts/copy_bundle_assets.js` copies `docs/users/` for qc-helper) and consumed by the published docs site (`docs-site/scripts/link-public-docs.mjs` symlinks `docs/users/` and `docs/developers/` into the Nextra build per `PUBLIC_DOC_ROOTS` and copies `docs/index.md` and `docs/_meta.ts`; the site discovers pages by walking that tree); consumers are runtime reads — qc-helper's doc paths, the i18n loader's segment-assembled `import()`, `/extensions new` scaffolds — never imports. Individual orphan locale keys stay class-4 candidates: their proof greps the literal key, naming its mechanism | | `docs-site/` | **Report-only** — standalone published-site app, not a workspace member; route files are consumed by Next.js filesystem routing and an out-of-repo deploy, never imports, and no in-repo CI builds it | | Tracked `.qwen/skills/**`, `.qwen/agents/**`, `.qwen/e2e-tests/**`, `docs/design/**`, `docs/plans/**` | **Report-only** — consumed by the skill loader, agent definitions, and process readers (including `AGENTS.md` itself), never imports | | `AGENTS.md`, `CLAUDE.md`, `SECURITY.md`, `CONTRIBUTING.md`, `.prettierrc.json`, `.prettierignore`, `.editorconfig`, `.nvmrc`, `.npmrc`, `.yamllint.yml` | **Report-only** — consumed by external tooling through filename convention (agent harnesses, GitHub's security-policy UI, prettier and yamllint config auto-discovery, nvm, editors); never imports, and an in-repo grep for them measures only prose | | Anything under `packages/core/src` | **Report-only** — published | | `packages/audio-capture`, `packages/channels` | **Report-only** — npm-published (`--access public`) | -| `packages/webui` | **Report-only** — npm-published under its own name; consumers import from the registry | | `packages/core/vendor/**`, `packages/web-shell` | **Report-only** — shipped inside the published `@qwen-code/qwen-code` tarball / served to browsers by `qwen serve`; consumers are bundled or browser-side, never imports | | `packages/cli/src/utils/**/*.sb` | **Report-only** — copied into the published bundle by extension glob (`scripts/copy_bundle_assets.js` copies `packages/**/*.sb`, `scripts/prepare-package.js` lists `'*.sb'`) and read at runtime through a segment-assembled path (`resolveSeatbeltProfileFile()` builds `sandbox-macos-${profile}.sb`); consumers are never imports, and a basename grep measures zero | | Any key in `packages/cli/src/config/settingsSchema.ts` | **Report-only** — see below | diff --git a/.qwen/skills/repo-hygiene/references/scan.md b/.qwen/skills/repo-hygiene/references/scan.md index 1b555373b3f..56815b9719d 100644 --- a/.qwen/skills/repo-hygiene/references/scan.md +++ b/.qwen/skills/repo-hygiene/references/scan.md @@ -108,13 +108,12 @@ hygiene findings. - Correct: multi-SDK behavior is consistent, protocol fields match the TS SDK, bridge error mapping preserves the original error class. -- **ui-apps** — the three UI apps (`packages/desktop-shell/`, - `packages/web-shell/`, `packages/webui/`). +- **ui-apps** — the two UI apps (`packages/desktop-shell/` and + `packages/web-shell/`). - `desktop-shell`: a thin Tauri shell around Web Shell (window management, process lifecycle, signing, updates). - `web-shell`: a client React app (`client/`), a Vite build, and a daemon proxy; ships as an embeddable component. - - `webui`: a lightweight web client consuming daemon REST endpoints. - Correct: IPC message shapes match both ends, routes resolve, state cleans up on unmount, portal roots are scoped. diff --git a/docs/design/web-shell/chat-transcript-contract-prevalidation.md b/docs/design/web-shell/chat-transcript-contract-prevalidation.md index 28d6198ddbd..934248ef0be 100644 --- a/docs/design/web-shell/chat-transcript-contract-prevalidation.md +++ b/docs/design/web-shell/chat-transcript-contract-prevalidation.md @@ -59,13 +59,13 @@ ChatTranscriptModel ### 2.1 当前生产边界 -| 消费端 | 当前事实 | 本方案处理 | -| ----------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Web/Qwen Server | daemon state 经 SDK reducer 产生 `DaemonTranscriptBlock[]`,完整 WebShell 渲染 | 保持生产路径不变;作为语义和兼容基线 | -| Qwen Tauri Desktop | 构建并复制同一 WebShell 产物 | 不增加 Desktop adapter;MR1 不认证安装产物行为 | -| VS Code | MR2A 继续由现有 legacy `MessageList` 渲染;不注册 transcript update、不引入 `@qwen-code/web-shell`、不发布死 feature flag | 该 render site 是 MR2B 的设计接入 seam,不新增空生产 adapter | -| HTML Export | CLI、Web API 和 VS Code 导出均把原始 records 交给 document projector,并使用版本绑定的产品模板 | 产品路径已收敛;无 records 的公共调用保留 legacy 兼容 | -| OpenWork/Craft Electron | 独立聊天实现 | 本方案范围外 | +| 消费端 | 当前事实 | 本方案处理 | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | +| Web/Qwen Server | daemon state 经 SDK reducer 产生 `DaemonTranscriptBlock[]`,完整 WebShell 渲染 | 保持生产路径不变;作为语义和兼容基线 | +| Qwen Tauri Desktop | 构建并复制同一 WebShell 产物 | 不增加 Desktop adapter;MR1 不认证安装产物行为 | +| VS Code | MR2A 继续由现有 legacy `MessageList` 渲染;不注册 transcript update、不引入 `@qwen-code/web-shell`、不发布死 feature flag | 该 render site 是 MR2B 的设计接入 seam,不新增空生产 adapter | +| HTML Export | CLI、Web API 和 VS Code 导出均把原始 records 交给 document projector,并使用版本绑定的产品模板 | 产品路径已收敛;legacy renderer 已随 `@qwen-code/webui` 退休移除,`toHtml` 要求 records(见 §12.4) | +| OpenWork/Craft Electron | 独立聊天实现 | 本方案范围外 | 当前 `WebShellTranscript`: @@ -99,7 +99,7 @@ ChatTranscriptModel 2. 复用现有 SDK reducer 和 `projectChatRecordsToDaemonTranscript()`,不复制 replay 规则; 3. 为 block、renderer item 和宿主动作建立可审计的稳定 identity; 4. 让 VS Code 复用 WebShell 聊天时间线,同时保留其 composer、权限、会话和原生操作; -5. 让 HTML Export 使用版本化、安全、资源有界且不主动联网的文档输入; +5. 让 HTML Export 使用版本化、安全、资源有界且不从文档内容主动联网的输入; 6. 保证 Web Shell interactive/readonly 和 Tauri Desktop 不发生功能回归; 7. 通过 fixture、hash、capability matrix 和自动化门禁使每个架构结论可重复验证; 8. 允许 VS Code 与 HTML 两条消费路径通过 MR2A/MR2B 独立评审、灰度、观察和回滚。 @@ -559,6 +559,7 @@ JSON Schema 无法表达 UTF-8 总字节、总文本、总图片和 envelope 预 - Markdown 图片与结构化 images 共用 MIME/来源/字节策略; - code、diff、shell、command 和普通文本中的 URL 字面量只作为文本,不触发资源加载; - 外部链接仅在明确用户点击时导航,去除 credential,并按策略处理 query/fragment; +- credential 去除的实际作用域是 http(s):自由文本中的内嵌 URL 由 `sanitizeEmbeddedUrls` 逐个改写,Markdown link/autolink 由 `normalizeNavigableUrl` 处理,两者都清空 userinfo、query 和 fragment;非 navigable scheme(`ssh user:pw@host`)、裸参数形式的凭据(`-pSECRET`、`--token=...`)以及 `code` / `inlineCode` 节点内的内容按原文导出。document boundary 不是通用 secret scanner,这条边界必须与代码保持一致,不能在文档里给出更强的承诺; - 超限时在富解析前输出安全占位和 diagnostic,不继续容错解析危险内容。 ### 10.8 document mode @@ -572,11 +573,12 @@ document mode 必须: - Markdown 远程图片不请求网络,危险 HTML/SVG 不执行; - Mermaid 限制、超时和 fallback 只在 document mode 启用,不能污染 interactive/readonly 的全局配置或缓存; - Mermaid、代码高亮、diff 和 chart 失败时保留可复制源码; -- 不加载需要 `unsafe-eval`、远程 WASM、远程 grammar、字体或动态 renderer 的资源。 +- 除版本绑定的 renderer 外,不加载需要 `unsafe-eval`、远程 WASM、远程 grammar、字体或其他动态资源。 -### 10.9 CSP 与零网络 +### 10.9 CSP 与登记网络 HTML 使用与 CLI build 精确绑定的 renderer。禁止 `latest`、版本范围和运行时远程解析。 +只有已将 renderer asset 发布到 npm 的 CLI 版本才能打开导出;两次发布之间的 source build 按设计 fail closed。 最低安全要求: @@ -585,7 +587,7 @@ HTML 使用与 CLI build 精确绑定的 renderer。禁止 `latest`、版本范 - `base-uri 'none'`、`form-action 'none'`; - images 只允许批准的 `data:` 或明确登记的同包资源; - script/style 使用 nonce/hash 或等价静态策略;若现有 React 需要 style attribute,只允许 `style-src-attr` 的最小例外,DTO 不接受 style 字段; -- V1 优先内联 renderer 必需资源;打开本地 HTML 后不得产生未登记 subrequest; +- renderer(包含 React runtime)只允许从 unpkg 的精确 npm 版本 URL 加载,并校验最终发布字节的 SRI;打开本地 HTML 后不得产生其他未登记 subrequest; - 浏览器测试拦截打开、展开、Markdown/Mermaid、主题和打印期间的全部请求;任何未登记请求或 CSP violation 立即失败。 ### 10.10 失败、完整性与 canary @@ -695,7 +697,7 @@ MR2A 中每项生产代码必须有 HTML 产品消费者。实施顺序: 2. **export builder**:实现 record policy、canonical projection、allowlist、opaque ID、metadata、budget 和 diagnostics; 3. **document mode**:实现非虚拟化/只读/无动作 renderer,Mermaid 限制仅在此 mode; 4. **HTML wiring**:CLI、Web API、VS Code `/export html` 和 integration runner 复用同一产品模板及版本绑定 renderer; -5. **browser/security gates**:CSP、零网络、canary、最大预算和版本失败测试; +5. **browser/security gates**:CSP、登记网络、canary、最大预算和版本失败测试; 6. **candidate evidence**:direct-daemon/ACP identity 只保留在 integration helper,不创建 VS Code 生产 adapter; 7. **gate state**:HTML capability 可标 PASS,但 `selectedVscodePath: null`、`overall: "fail"` 保持不变。 @@ -723,6 +725,17 @@ MR2B 从 MR2A 之后开始,并由真实 VS Code consumer 驱动: MR2A 先独立收敛产品 HTML Export。MR2B 再接入 VS Code live timeline,避免 renderer、transport、host actions、VSIX 与许可证变更挤入同一评审。JSON Schema 无法表达的 credential URL、脱敏 path、总字节和资源预算继续由小型语义安全层负责,不恢复重复的逐字段结构 validator。 +### 12.4 legacy HTML renderer 退休 + +MR2A 落地后,`@qwen-code/webui` 只剩 HTML Export 的 legacy 回退这一个消费者。该 package 随后被整体退休,legacy renderer 一并移除。删除证据: + +1. **无剩余产品消费者**:CLI `/export html`、Web API `session-export` 和 VS Code `/export html` 都无条件传入原始 records,因此 legacy 分支在产品路径上不可达; +2. **接口收紧**:`toHtml(sessionData, originalRecords)` 的第二参改为必填,`loadHtmlTemplate` 与 `injectDataIntoHtmlTemplate` 连同 UMD/CDN 模板一并删除,document renderer 成为唯一 HTML 导出实现; +3. **回退被显式拒绝而非静默降级**:integration runner 遇到 legacy exported JSONL 时直接报错,要求提供 source ChatRecord JSONL,避免用一条未经 allowlist 的路径渲染旧文件; +4. **防回归**:CI 增加 `check:no-webui`,拒绝重新引入该 package 或其依赖。 + +本节只覆盖 legacy HTML renderer。VS Code legacy `MessageList` 不在此范围内,其移除仍受 §15 的观察期与删除证据约束。 + ## 13. 验证架构与测试矩阵 ```mermaid @@ -764,12 +777,12 @@ MR1 不以源码文本断言认证 Desktop 打包行为。Web/Tauri 的现有构 | VS Code | MR2A 验证 legacy timeline 与 `/export html`;MR2B 验证选定路径、scope/generation、callbacks、feature flag、legacy parity | | Web Shell | interactive/readonly raw 兼容、document safe-only、render/action identity | | Export builder | record policy、per-kind allowlist、opaque IDs、metadata、diagnostic、version | -| Browser | schema failure、zero network、CSP、canary、find/copy/print、最大预算 | +| Browser | schema failure、登记网络、CSP、canary、find/copy/print、最大预算 | | Packaging | Web/Tauri regression、VSIX 三平台、CLI renderer 版本绑定、integration runner 收敛 | Passing test 也必须反向审计:测试是否断言了正确语义、是否加载当前构建产物、是否真的覆盖真实消费者,不能用静态 source assertion 替代浏览器或 VSIX 行为验证。 -当前 MR2A 验证结果:SDK、Core、CLI、Web Shell、VS Code `/export html` 聚焦测试和 direct-daemon/ACP integration candidate gate 已通过;产品 HTML 已完成构建、Node 侧安全断言和真实 Chromium browser gate,concurrent runner 也复用同一产品收集、归一化和 formatter。browser gate 已覆盖最大文档、真实产品入口、零网络、主动 CSP 违规、canary、搜索、复制、打印、远程资源降级和 epoch 时间戳排除。VS Code live timeline、scope/generation/reconnect、宿主动作、VSIX 与 packaged artifact 证据全部属于 MR2B。 +当前 MR2A 验证结果:SDK、Core、CLI、Web Shell、VS Code `/export html` 聚焦测试和 direct-daemon/ACP integration candidate gate 已通过;产品 HTML 已完成构建、Node 侧安全断言和真实 Chromium browser gate,concurrent runner 也复用同一产品收集、归一化和 formatter。browser gate 已覆盖最大文档、真实产品入口、登记网络、主动 CSP 违规、canary、搜索、复制、打印、远程资源降级和 epoch 时间戳排除。VS Code live timeline、scope/generation/reconnect、宿主动作、VSIX 与 packaged artifact 证据全部属于 MR2B。 ## 14. 门禁 @@ -866,6 +879,6 @@ Web/Qwen 和 Tauri 不迁移。若 MR2A/MR2B 对共享组件的改动导致默 - HTML 产品路径与 integration runner 不再维护第二套 renderer; - Web/Qwen Server 和 Tauri Desktop 默认行为无回归; - security、network、budget、CSP、version、VSIX/CLI packaging 和观察期完成; -- legacy HTML renderer 与 VS Code timeline 只有在各自有删除证据时才移除; +- legacy HTML renderer 已移除,删除证据见 §12.4;VS Code legacy timeline 仍只有在有删除证据时才移除; - 未引入新的跨宿主 ChatPanel 包、通用消息模型或 OpenWork overlay; - 后续任何公共契约变化继续更新本文档,不创建平行设计来源。 diff --git a/docs/developers/architecture.md b/docs/developers/architecture.md index f3ba6ee0be6..7c6a2c88786 100644 --- a/docs/developers/architecture.md +++ b/docs/developers/architecture.md @@ -83,8 +83,7 @@ an HTTP daemon. See the | `packages/core` | UI-independent agent orchestration, model-provider integration, prompt and context construction, tool registration and execution, permissions, sessions, memory, telemetry, and shared services. | | `packages/acp-bridge` | ACP channel lifecycle, session multiplexing, event delivery, permission mediation, process spawning, and the filesystem seam shared by daemon and adapter hosts. | | `packages/sdk-typescript` | Programmatic process execution through `query()` plus HTTP/SSE clients and transcript projection for `qwen serve`. | -| `packages/webui` | Shared React components and the daemon React adapter built on the TypeScript SDK. | -| `packages/web-shell` | The terminal-style browser UI built on `packages/webui` and the daemon SDK. | +| `packages/web-shell` | The browser UI and daemon React adapter built on the TypeScript SDK. | | `packages/web-templates` | Web templates packaged as embeddable JavaScript and CSS strings. | | `packages/audio-capture` | Native microphone capture for voice input. | | `packages/channels` | The shared channel runtime and platform adapters for messaging services. | @@ -111,8 +110,8 @@ of the interactive, headless, ACP, daemon, channel, or maintenance flows. Presentation remains outside the core runtime: - the Ink TUI renders local interactive sessions; -- `packages/webui` adapts daemon state to React providers and hooks; -- `packages/web-shell` provides the browser terminal experience; +- `packages/web-shell` adapts daemon state to React providers and hooks and + provides the browser experience; - IDE and channel packages translate host-specific events into shared client or bridge contracts. @@ -153,8 +152,8 @@ The TypeScript SDK exposes two client styles: - `query()` starts and controls a Qwen Code process for programmatic local use; - daemon clients communicate with `qwen serve` over HTTP and SSE. -`packages/webui` builds a React state layer on the daemon client, and -`packages/web-shell` builds the browser UI on that state layer. Other clients, +`packages/web-shell` builds a React state layer and browser UI on the daemon +client. Other clients, including IDE integrations and daemon-managed channels, reuse the same SDK and event contracts instead of importing server implementation code. diff --git a/docs/developers/daemon-client-adapters/web-ui.md b/docs/developers/daemon-client-adapters/web-shell.md similarity index 86% rename from docs/developers/daemon-client-adapters/web-ui.md rename to docs/developers/daemon-client-adapters/web-shell.md index 2aa022fa371..c4c23856ccd 100644 --- a/docs/developers/daemon-client-adapters/web-ui.md +++ b/docs/developers/daemon-client-adapters/web-shell.md @@ -1,4 +1,4 @@ -# Daemon Web UI Adapter +# Web Shell Daemon Adapter ## Goal @@ -26,16 +26,16 @@ The split is: - `normalizeDaemonEvent()` converts daemon wire events into UI events. - `createDaemonTranscriptStore()` reduces UI events into transcript blocks. -React clients can use the optional `@qwen-code/webui` binding: +React clients can use the binding exported by Web Shell: ```tsx import { DaemonSessionProvider, - useDaemonActions, - useDaemonConnection, - useDaemonPendingPermissions, - useDaemonTranscriptBlocks, -} from '@qwen-code/webui'; + useActions, + useConnection, + usePendingPermissions, + useTranscriptBlocks, +} from '@qwen-code/web-shell/daemon-react-sdk'; ``` Minimal React shape: @@ -51,7 +51,7 @@ function App() { } function Transcript() { - const blocks = useDaemonTranscriptBlocks(); + const blocks = useTranscriptBlocks(); return blocks.map((block) => ); } ``` @@ -105,13 +105,15 @@ Ink rendering. - The native `qwen` TUI remains direct and unchanged. - `--acp`, channel, and IDE paths remain unchanged by default. - The SDK UI core is additive. -- The WebUI React binding is optional and only runs in clients that import it. +- The Web Shell React binding is optional and only runs in clients that import + it. - Removed daemon TUI spike code should not be treated as a product migration. ## Follow-Ups -- Add a daemon-served local `/web` POC or equivalent same-origin web app. -- Build first-class chat and terminal renderers on top of transcript blocks. +- Keep the daemon-served Web Shell and embedded IDE host behavior aligned. +- Continue building first-class chat and terminal renderers on transcript + blocks. - Add richer typed events only where existing daemon events are too low-level for stable browser UI behavior. - Consider a dedicated `@qwen-code/daemon-ui-core` package if non-SDK consumers diff --git a/docs/developers/daemon/00-index.md b/docs/developers/daemon/00-index.md index da582cd8077..2483cc1419a 100644 --- a/docs/developers/daemon/00-index.md +++ b/docs/developers/daemon/00-index.md @@ -97,7 +97,7 @@ Use these anchors when moving from the docs into the latest `main` code: | Capabilities | `packages/cli/src/serve/capabilities.ts`, `mcp_server_restart_refused.reason`, `MCP_RESTART_REFUSED_REASONS.has` | [`11`](./11-capabilities-versioning.md) | | Auth and device flow | `packages/cli/src/serve/auth.ts`, `packages/cli/src/serve/auth/device-flow.ts` | [`12`](./12-auth-security.md) | | TypeScript SDK daemon client | `packages/sdk-typescript/src/daemon/{DaemonClient,DaemonSessionClient,DaemonAuthFlow,sse,events,types}.ts`, `MCP_RESTART_DEFAULT_TIMEOUT_MS` | [`13`](./13-sdk-daemon-client.md) | -| Shared UI transcript layer | `DaemonUiEventType`, `DaemonSessionProvider`, `packages/webui/src/daemon/` | [`13`](./13-sdk-daemon-client.md), [`14`](./14-cli-tui-adapter.md), [`../daemon-ui/README.md`](../daemon-ui/README.md) | +| Shared UI transcript layer | `DaemonUiEventType`, `DaemonSessionProvider`, `packages/web-shell/client/daemon/` | [`13`](./13-sdk-daemon-client.md), [`14`](./14-cli-tui-adapter.md), [`../daemon-ui/README.md`](../daemon-ui/README.md) | | Channels and IDE adapters | `packages/channels/`, `packages/vscode-ide-companion/src/services/daemonIdeConnection.ts` | [`15`](./15-channel-adapters.md), [`16`](./16-vscode-ide-adapter.md) | ## What is intentionally out of scope @@ -136,7 +136,7 @@ Use these anchors when moving from the docs into the latest `main` code: | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | TypeScript SDK daemon client | `DaemonClient`, `DaemonSessionClient`, `DaemonAuthFlow`, SSE parser, event reducers, feature preflight, and UI transcript exports are documented. | [`13`](./13-sdk-daemon-client.md) | | Shared UI transcript layer | SDK `daemon/ui/*` normalizes daemon events into 42 UI semantic event types, reduces them into transcript blocks, and provides renderers/conformance helpers. | [`14`](./14-cli-tui-adapter.md), [`../daemon-ui/README.md`](../daemon-ui/README.md), [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md) | -| Web UI daemon consumer | `packages/webui/src/daemon/` consumes the SDK transcript store through React providers and adapters. | [`14`](./14-cli-tui-adapter.md), [`../daemon-client-adapters/web-ui.md`](../daemon-client-adapters/web-ui.md) | +| Web Shell daemon consumer | `packages/web-shell/client/daemon/` consumes the SDK transcript store through React providers and adapters. | [`14`](./14-cli-tui-adapter.md), [`../daemon-client-adapters/web-shell.md`](../daemon-client-adapters/web-shell.md) | | CLI TUI / channels / VS Code | Legacy paths still exist; migration to shared transcript primitives is documented as follow-up work, not completed behavior. | [`14`](./14-cli-tui-adapter.md), [`15`](./15-channel-adapters.md), [`16`](./16-vscode-ide-adapter.md) | ### Reference and operations diff --git a/docs/developers/daemon/01-architecture.md b/docs/developers/daemon/01-architecture.md index 081c0339fdd..d7109b95128 100644 --- a/docs/developers/daemon/01-architecture.md +++ b/docs/developers/daemon/01-architecture.md @@ -13,7 +13,7 @@ This doc gives the **system-level picture** that the rest of this documentation ```mermaid flowchart LR subgraph clients["Clients"] - WUI["Web UI
(packages/webui/src/daemon)"] + WUI["Web Shell
(packages/web-shell/client/daemon)"] TUI["CLI TUI
(packages/cli/src/ui/daemon)"] IDE["VS Code IDE
(packages/vscode-ide-companion)"] CH["Channel bots
(DingTalk / WeChat / Telegram / Feishu)"] @@ -98,7 +98,7 @@ flowchart TB end subgraph adapters["Adapters"] - WUIP["webui/src/daemon/
DaemonSessionProvider.tsx"] + WUIP["web-shell/client/daemon/
DaemonSessionProvider.tsx"] TUIA["cli/src/ui/daemon/
daemon-tui-adapter.ts"] CHB["channels/base/
DaemonChannelBridge.ts"] DT["channels/dingtalk"] diff --git a/docs/developers/daemon/08-session-lifecycle.md b/docs/developers/daemon/08-session-lifecycle.md index 29d92fc2760..61703c865fa 100644 --- a/docs/developers/daemon/08-session-lifecycle.md +++ b/docs/developers/daemon/08-session-lifecycle.md @@ -57,7 +57,7 @@ Under `sessionScope: 'thread'`, each thread can mint a distinct session. The cal `X-Qwen-Client-Id` is **optional** but **strongly recommended**. The daemon does not generate one on the caller's behalf — clients pick their own and reuse it across requests so the daemon can attribute votes, audit events, and detect reconnects. -Each independent controller should use a distinct, stable ID. The WebUI generates IDs with a `webui_` prefix by default. A host and an embedded WebShell should share an ID only when they intentionally act as one logical controller; once shared, daemon logs cannot distinguish which one originated a request. +Each independent controller should use a distinct, stable ID. Web Shell preserves the historical `webui_` prefix for compatibility. A host and an embedded Web Shell should share an ID only when they intentionally act as one logical controller; once shared, daemon logs cannot distinguish which one originated a request. Validation rules: diff --git a/docs/developers/daemon/13-sdk-daemon-client.md b/docs/developers/daemon/13-sdk-daemon-client.md index 0d2e8c43892..3b7d3074cb1 100644 --- a/docs/developers/daemon/13-sdk-daemon-client.md +++ b/docs/developers/daemon/13-sdk-daemon-client.md @@ -324,7 +324,7 @@ set of primitives that turn daemon events into transcript blocks: - Public constants include `DAEMON_PLAN_TOOL_CALL_ID`. - `conformance.ts` contains the cross-host consistency test suite. -The first production consumer is `packages/webui/src/daemon/` through React's +The first production consumer is `packages/web-shell/client/daemon/` through React's `DaemonSessionProvider`. See [`14-cli-tui-adapter.md`](./14-cli-tui-adapter.md) for the detailed architecture, glossary, selector table, and relationship to the legacy `DaemonTuiAdapter`. diff --git a/docs/developers/daemon/14-cli-tui-adapter.md b/docs/developers/daemon/14-cli-tui-adapter.md index 7902e22c035..b77d2cc7a87 100644 --- a/docs/developers/daemon/14-cli-tui-adapter.md +++ b/docs/developers/daemon/14-cli-tui-adapter.md @@ -11,7 +11,7 @@ - **Renderers** (`render.ts`, `terminal.ts`, `toolPreview.ts`): transcript blocks to HTML, terminal text, and tool preview strings. Hosts can use or replace them. - **Conformance** (`conformance.ts`): cross-host consistency tests used when channel, TUI, and IDE surfaces migrate to these primitives. -The first production consumer is **`packages/webui/src/daemon/`** ([#4328](https://github.com/QwenLM/qwen-code/pull/4328)). Its React `DaemonSessionProvider` and transcript adapter let the web UI connect directly to daemon HTTP+SSE instead of only rendering host `postMessage` traffic. CLI TUI, channel base, and VS Code IDE can reuse the same layer later; [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md) documents the v2 incremental migration guide. +The first production consumer was introduced in [#4328](https://github.com/QwenLM/qwen-code/pull/4328) and now lives in **`packages/web-shell/client/daemon/`**. Its React `DaemonSessionProvider` and transcript adapter let Web Shell connect directly to daemon HTTP+SSE. CLI TUI, channel base, and VS Code IDE can reuse the same layer; [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md) documents the v2 incremental migration guide. ## Responsibilities @@ -132,17 +132,19 @@ Hosts can stop at `(E)` and implement their own reducer, or consume `(G)` and th ## Consumers -### `packages/webui/src/daemon/` +### `packages/web-shell/client/daemon/` This landed in [#4328](https://github.com/QwenLM/qwen-code/pull/4328). -| File | Exports | -| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `DaemonSessionProvider.tsx` | React ``; `useDaemonSession()`, `useDaemonTranscriptStore()`, `useDaemonTranscriptState()`, `useDaemonTranscriptBlocks()`, `useDaemonPendingPermissions()`, `useDaemonActions()`, `useDaemonConnection()` hooks; `DaemonConnectionStatus`, `DaemonConnectionState`, `DaemonSessionContextValue` types | -| `transcriptAdapter.ts` | Adapts SDK `DaemonTranscriptBlock` into the web UI's `UnifiedMessage`, including markdown streaming chunk merge and tool call summaries | -| `index.ts` | Subpackage barrel | +| File | Exports | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `session/DaemonSessionProvider.tsx` | React ``; `useDaemonSession()`, `useDaemonTranscriptStore()`, `useDaemonTranscriptState()`, `useDaemonTranscriptBlocks()`, `useDaemonPendingPermissions()`, `useDaemonActions()`, `useDaemonConnection()` hooks; `DaemonConnectionStatus`, `DaemonConnectionState`, `DaemonSessionContextValue` types | +| `session/index.ts` | Session barrel: provider, hooks, and session types | +| `index.ts` | Subpackage barrel | -The web UI can now connect directly to daemon HTTP+SSE and render a transcript. The old `ACPAdapter` host `postMessage` path remains available. +The transcript adapter lives outside this directory: `packages/web-shell/client/adapters/transcriptAdapter.ts` exports only `extractPendingPermission(blocks): PermissionRequest | null`, lifting unresolved SDK permission blocks for host UIs. Transcript blocks themselves flow through the SDK `ui/*` layer and `useDaemonTranscriptBlocks()`; there is no `UnifiedMessage` adapter in Web Shell. + +The web UI can now connect directly to daemon HTTP+SSE and render a transcript. The old `ACPAdapter` host `postMessage` path was retired with the legacy WebUI workspace; webviews now embed Web Shell for rendering (see [`16-vscode-ide-adapter.md`](./16-vscode-ide-adapter.md)). ### Later migrations @@ -163,9 +165,9 @@ The web UI can now connect directly to daemon HTTP+SSE and render a transcript. ## Dependencies - Upstream wire types: `packages/sdk-typescript/src/daemon/events.ts` (see [`09-event-schema.md`](./09-event-schema.md)). -- Real downstream consumer: `packages/webui/src/daemon/`. +- Real downstream consumer: `packages/web-shell/client/daemon/`. - Later migration targets: `packages/cli/src/ui/`, `packages/channels/base/`, and `packages/vscode-ide-companion/src/services/daemonIdeConnection.ts`. -- Parallel references: [`../daemon-ui/README.md`](../daemon-ui/README.md), [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md), and [`../daemon-client-adapters/web-ui.md`](../daemon-client-adapters/web-ui.md). +- Parallel references: [`../daemon-ui/README.md`](../daemon-ui/README.md), [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md), and [`../daemon-client-adapters/web-shell.md`](../daemon-client-adapters/web-shell.md). ## Configuration @@ -176,7 +178,7 @@ The web UI can now connect directly to daemon HTTP+SSE and render a transcript. ## Caveats and known limits - **`daemon-tui-adapter.ts` still exists**. It is the CLI package's legacy experimental adapter. New code should prefer SDK `ui/*`: `normalizeDaemonEvent`, `reduceDaemonTranscriptEvents`, and `DaemonTranscriptBlock`. -- **CLI TUI, channel base, and VS Code IDE are not migrated yet**. They still maintain their own rendering logic. The `docs/developers/daemon-client-adapters/` directory still has `ide.md`, `channel-web.md`, and the historical `tui.md` draft; the newer `web-ui.md` covers the web UI adapter design. +- **CLI TUI, channel base, and VS Code IDE are not migrated yet**. They still maintain their own rendering logic. The `docs/developers/daemon-client-adapters/` directory still has `ide.md`, `channel-web.md`, and the historical `tui.md` draft; the newer `web-shell.md` covers the web UI adapter design. - **`eventId` is the primary ordering key**. `createdAt` remains as a deprecated alias (`clientReceivedAt`). New code should use `selectTranscriptBlocksOrderedByEventId(state)`. `MIGRATION.md` shows the code diff for switching from `createdAt` ordering to `eventId` ordering. - **Unknown wire types normalize to `debug`**. They are no longer dropped as in the old adapter. Renderers do not show `debug` by default; hosts must opt in to display it. - **Bundle size**: the `ui/*` subpackage is exported as an ESM subpath through `@qwen-code/sdk/daemon` and does not pull in React or DOM dependencies. React integration is only loaded when a web UI consumer uses `DaemonSessionProvider`. @@ -188,6 +190,6 @@ The web UI can now connect directly to daemon HTTP+SSE and render a transcript. - `packages/sdk-typescript/src/daemon/ui/normalizer.ts` (wire-to-UI mapping) - `packages/sdk-typescript/src/daemon/ui/store.ts`, `render.ts`, `terminal.ts`, `toolPreview.ts`, `conformance.ts` - `packages/sdk-typescript/src/daemon/index.ts` (`ui/*` re-export block) -- `packages/webui/src/daemon/DaemonSessionProvider.tsx`, `transcriptAdapter.ts` -- Upstream docs: [`../daemon-ui/README.md`](../daemon-ui/README.md), [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md), [`../daemon-client-adapters/web-ui.md`](../daemon-client-adapters/web-ui.md) +- `packages/web-shell/client/daemon/session/DaemonSessionProvider.tsx`, `packages/web-shell/client/adapters/transcriptAdapter.ts` +- Upstream docs: [`../daemon-ui/README.md`](../daemon-ui/README.md), [`../daemon-ui/MIGRATION.md`](../daemon-ui/MIGRATION.md), [`../daemon-client-adapters/web-shell.md`](../daemon-client-adapters/web-shell.md) - Context PRs: [#4328](https://github.com/QwenLM/qwen-code/pull/4328) (v1 transcript layer and web UI provider), [#4353](https://github.com/QwenLM/qwen-code/pull/4353) (v2 unified completeness follow-up) diff --git a/docs/developers/daemon/16-vscode-ide-adapter.md b/docs/developers/daemon/16-vscode-ide-adapter.md index 98b6ff0ee36..3b7c2ebfe29 100644 --- a/docs/developers/daemon/16-vscode-ide-adapter.md +++ b/docs/developers/daemon/16-vscode-ide-adapter.md @@ -85,7 +85,7 @@ exception paths call `onEndTurn('error')`. ### Webview bridging -The connection class is **transport-only**. The actual VS Code integration lives in `packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts` (and friends). The provider subscribes to the connection's callbacks and translates them into webview `postMessage` calls. The webview itself uses the shared `packages/webui/` component library to render — see Adapter Matrix in [`01-architecture.md`](./01-architecture.md). +The connection class is **transport-only**. The actual VS Code integration lives in `packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts` (and friends). The provider subscribes to the connection's callbacks and translates them into webview `postMessage` calls. The webview embeds Web Shell for rendering. ### Connect serialization @@ -174,7 +174,7 @@ sequenceDiagram - `packages/sdk-typescript/src/daemon/` — `DaemonClient`, `DaemonSessionClient` (the actual transport). - VS Code extension API (`vscode.*`) — host APIs, quick-pick, webview. -- `packages/webui/src/adapters/ACPAdapter.ts` — webview rendering of ACP-shaped messages relayed via `postMessage`. +- `packages/web-shell/client/` — embedded webview rendering for daemon session events. ## Configuration @@ -201,6 +201,6 @@ sequenceDiagram - `packages/vscode-ide-companion/src/services/daemonIdeConnection.ts` (`createSdkDaemonSessionFactory`) - `packages/vscode-ide-companion/src/types/connectionTypes.ts` (legacy `AcpConnectionState`) - `packages/vscode-ide-companion/src/webview/providers/ChatWebviewViewProvider.ts` (webview bridge) -- `packages/webui/src/adapters/ACPAdapter.ts` (webview ACP-message adapter) +- `packages/web-shell/client/` (embedded Web Shell renderer) - Draft design: [`../daemon-client-adapters/ide.md`](../daemon-client-adapters/ide.md) - SDK reference: [`13-sdk-daemon-client.md`](./13-sdk-daemon-client.md) diff --git a/docs/developers/examples/daemon-client-quickstart.md b/docs/developers/examples/daemon-client-quickstart.md index eb365898bcc..444402fd3ac 100644 --- a/docs/developers/examples/daemon-client-quickstart.md +++ b/docs/developers/examples/daemon-client-quickstart.md @@ -247,7 +247,7 @@ const client = new DaemonClient({ const client = new DaemonClient({ baseUrl: 'https://your-host:4170' }); ``` -The fallback strips leading/trailing whitespace (handy for `export QWEN_SERVER_TOKEN="$(cat token.txt)"` where `cat` adds a newline) and treats empty / whitespace-only values as unset (a stale `export QWEN_SERVER_TOKEN=""` won't accidentally send `Authorization: Bearer ` with no token). The fallback runs once at construction; later `process.env` mutations don't affect already-built clients. Browser bundles (e.g. via `@qwen-code/webui`) get `undefined` cleanly because `globalThis.process` doesn't exist there. +The fallback strips leading/trailing whitespace (handy for `export QWEN_SERVER_TOKEN="$(cat token.txt)"` where `cat` adds a newline) and treats empty / whitespace-only values as unset (a stale `export QWEN_SERVER_TOKEN=""` won't accidentally send `Authorization: Bearer ` with no token). The fallback runs once at construction; later `process.env` mutations don't affect already-built clients. Browser bundles (e.g. via `@qwen-code/web-shell`) get `undefined` cleanly because `globalThis.process` doesn't exist there. Wrong / missing tokens return `401` with a uniform body — the SDK throws `DaemonHttpError` on any 4xx/5xx from a route handler. diff --git a/docs/developers/qwen-serve-protocol.md b/docs/developers/qwen-serve-protocol.md index b01dae0c682..90544fbf787 100644 --- a/docs/developers/qwen-serve-protocol.md +++ b/docs/developers/qwen-serve-protocol.md @@ -22,7 +22,7 @@ Channel webhook ingress (`POST /channels/:channelName/webhooks/:source`) is sepa When the flag is on, the global `bearerAuth` middleware gates **every normal API route** — including `/health` and `/capabilities`. Channel webhook ingress remains independently shared-secret-authenticated, and Web Shell document and asset routes remain pre-auth. An **unauthenticated** client therefore cannot pre-flight `caps.features` to discover that auth is required: the discovery surface for that case is the **401 response body** itself (uniform across bearer-gated routes per the [Authentication](#authentication) section). The `require_auth` capability tag is a **post-authentication confirmation** — once a client successfully authenticates and reads `/capabilities`, the tag's presence confirms the daemon was started with `--require-auth` (useful for audit / compliance UIs and for SDK clients to surface "this deployment is hardened" in a settings panel). Strict mutation routes accept trusted-loopback primary-listener requests, bearer-authenticated requests, or paired Local Control requests. Non-trusted token-less embeds still receive `401 { code: "token_required", error: "…" }`; with `--require-auth`, global bearer middleware rejects first with the legacy `Unauthorized` body. -**`--allow-origin ` (T2.4 [#4514](https://github.com/QwenLM/qwen-code/issues/4514)).** Browser webuis hitting the daemon cross-origin are blocked by default — any request carrying an `Origin` header returns `403 {"error":"Request denied by CORS policy"}` because CLI/SDK clients never send `Origin` and the daemon treats its presence as a sign the request came from a browser context the operator has not opted into. Pass `--allow-origin ` (repeatable) at boot to install an allowlist instead of the wall. Each pattern is either: +**`--allow-origin ` (T2.4 [#4514](https://github.com/QwenLM/qwen-code/issues/4514)).** Browser clients hitting the daemon cross-origin are blocked by default — any request carrying an `Origin` header returns `403 {"error":"Request denied by CORS policy"}` because CLI/SDK clients never send `Origin` and the daemon treats its presence as a sign the request came from a browser context the operator has not opted into. Pass `--allow-origin ` (repeatable) at boot to install an allowlist instead of the wall. Each pattern is either: - The literal `*` — admit any origin. **Risky**: boot refuses when `*` is configured but no bearer token is set (any source: `--token`, `QWEN_SERVER_TOKEN`, or `--require-auth` which mandates a token at boot). The boot breadcrumb emits a stderr warning when `*` is in the list. **Recommendation**: pair with `--require-auth` on loopback binds so `/health` is also gated by the bearer — it's registered before the bearer middleware on loopback by default (so k8s/Compose probes can reach it without a token), and a `*` allowlist makes it reachable from any cross-origin browser. `--require-auth` still leaves the Web Shell static assets (`/`, `/assets/*`, and `/session/:id` document navigations) pre-auth on loopback by design — they are mounted before the bearer middleware — so under a `*` allowlist they remain readable from any cross-origin browser; `--no-web` removes that surface. On non-loopback binds the bearer is already mandatory at boot and `/health` is registered behind it. Normal API routes are bearer-gated, channel webhook ingress retains its own shared-secret gate, and Web Shell static assets (`/`, `/assets/*`, and `/session/:id` document navigations) remain pre-auth unless `--no-web` removes them. - A canonical URL origin — `://[:]`. **No trailing slash, no path, no userinfo, no query.** Boot refuses with `InvalidAllowOriginPatternError` if the entry fails the round-trip `new URL(pattern).origin === pattern`; the error message names the bad pattern and the canonical form. Strict-by-intent: silent normalization (e.g. trimming a trailing `/`) would let typos slip through and accept ambiguous input. Without a token, HTTP(S) entries are limited to loopback hosts; a non-loopback browser origin requires a token because it can otherwise drive the full operator API, including code execution as the daemon user. Explicit browser-extension origins keep their existing tokenless local-automation path. Startup logs the authority granted to any tokenless allowed browser origin. @@ -38,13 +38,13 @@ Access-Control-Max-Age: 86400 Access-Control-Expose-Headers: Retry-After, X-Qwen-Event-Epoch, X-Qwen-SSE-Stream-Id ``` -`Access-Control-Allow-Origin` echoes the request's origin verbatim (lowercase / uppercase as the browser sent it) rather than the literal `*`, even under the `*` pattern — browser caches key responses on it paired with `Vary: Origin`, and echoing leaves room to add `Access-Control-Allow-Credentials` in a later release without a schema change. The exposed headers let browser webuis honor retry hints, retain the SSE epoch, and correlate accepted physical streams. `Access-Control-Allow-Credentials` is **NOT** sent today: configured daemon credentials use bearer-in-`Authorization`, which works cross-origin without `credentials: 'include'`; trusted-loopback authority needs no browser credential. +`Access-Control-Allow-Origin` echoes the request's origin verbatim (lowercase / uppercase as the browser sent it) rather than the literal `*`, even under the `*` pattern — browser caches key responses on it paired with `Vary: Origin`, and echoing leaves room to add `Access-Control-Allow-Credentials` in a later release without a schema change. The exposed headers let browser clients honor retry hints, retain the SSE epoch, and correlate accepted physical streams. `Access-Control-Allow-Credentials` is **NOT** sent today: configured daemon credentials use bearer-in-`Authorization`, which works cross-origin without `credentials: 'include'`; trusted-loopback authority needs no browser credential. OPTIONS preflight requests (OPTIONS with `Access-Control-Request-Method` or `Access-Control-Request-Headers`) short-circuit with `204 No Content` plus the headers above. This is the conventional CORS pattern and is safe — the preflight only confirms which methods/headers the daemon will accept; the actual subsequent request still runs the Host gate and then either bearer/listener authority or the channel webhook shared-secret gate before any state is read or mutated. Plain OPTIONS requests from matched origins keep flowing downstream with CORS headers attached. Origins that don't match the allowlist still get `403 {"error":"Request denied by CORS policy"}` — same envelope as the default wall, so clients that already parsed the wall's response don't have to special-case allowlist-deployed daemons. The reject path **does not** emit any `Access-Control-*` headers (the browser would ignore them, and emitting would indirectly advertise the allowlist size through header presence). -The configured pattern list is intentionally NOT echoed in `/capabilities` — browser webui already knows its own origin (it called the daemon, after all), and surfacing the list would let an unauthenticated reader of `/capabilities` enumerate every trusted origin (useful recon for a misconfigured deployment). SDK clients gate on the `caps.features.allow_origin` tag for "this daemon honors cross-origin browser hits" without needing to know which specific origins. +The configured pattern list is intentionally NOT echoed in `/capabilities` — a browser client already knows its own origin (it called the daemon, after all), and surfacing the list would let an unauthenticated reader of `/capabilities` enumerate every trusted origin (useful recon for a misconfigured deployment). SDK clients gate on the `caps.features.allow_origin` tag for "this daemon honors cross-origin browser hits" without needing to know which specific origins. Loopback self-origin requests (e.g. the Web Shell calling the daemon at the same `127.0.0.1:port`) are handled by a **separate** Origin-strip shim that runs BEFORE the CORS middleware and removes the `Origin` header for `127.0.0.1:port` / `localhost:port` / `[::1]:port` / `host.docker.internal:port` or the exact bound loopback address and port. It also accepts the scheme-matched port-less forms that browsers send for default ports: `http://host` on port 80 and `https://host` on port 443. These requests pass through regardless of `--allow-origin` configuration — operators don't need to list the daemon's own port to make the Web Shell work. @@ -282,7 +282,7 @@ Runtime status and ensure responses use this shape: `session_load` and `session_resume` advertise the explicit-restore routes (`POST /session/:id/load` and `POST /session/:id/resume`). Older daemons return `404` for these paths, so SDK clients should pre-flight `caps.features` before calling. `unstable_session_resume` is still advertised as a deprecated alias for compatibility with SDKs that shipped while the underlying ACP method was named `connection.unstable_resumeSession`; new clients should gate on `session_resume`. -`limits.sessionRestoreTimeoutMs`, when present, is the daemon's wall-clock budget for the underlying ACP `loadSession` / `unstable_resumeSession` request. It is an additive v1 field. The TypeScript SDK gives the daemon 10 seconds of client headroom, and the WebUI watchdog gives it 15 seconds; clients talking to an older daemon should use 70 seconds and 75 seconds respectively. +`limits.sessionRestoreTimeoutMs`, when present, is the daemon's wall-clock budget for the underlying ACP `loadSession` / `unstable_resumeSession` request. It is an additive v1 field. The TypeScript SDK gives the daemon 10 seconds of client headroom, and the Web Shell watchdog gives it 15 seconds; clients talking to an older daemon should use 70 seconds and 75 seconds respectively. `session_transcript` advertises `GET /session/:id/transcript`, a read-only paged replay view over the persisted active-session JSONL. It is separate from `/load`: it does not attach a client, seed the live EventBus, create a live session, or change the live replay window. Clients should use it when they need the complete on-disk transcript for a long session, and continue using `/load` only for bounded live replay during cold UI restore. @@ -556,7 +556,7 @@ operator diagnostic snapshot documented below. | `mcp_workspace_pool` | the shared MCP transport pool is active. Omitted when `QWEN_SERVE_NO_MCP_POOL=1` disables the pool. | | `mcp_pool_restart` | the shared MCP transport pool is active; restart responses may include pool-aware multi-entry shapes. | | `external_tool_guard` | `qwen serve` completed the startup handshake for `--external-tool-guard-mode=required`; every spawned ACP channel must acknowledge the installed callback before Session creation, and every supported top-level managed ACP tool invocation that reaches the final execution boundary must receive one external pre-execution allow. Earlier permission/hook denials make no provider request. Nested AgentCore execution is outside v1 and is rejected while this external provider mode is active. The tag reflects only the external provider: independently of it, every daemon applies the built-in Git relocation guard to the managed tools that carry a shell command line (`run_shell_command` and `monitor`), so the absence of this tag does not mean no pre-execution denials. | -| `allow_origin` | T2.4 ([#4514](https://github.com/QwenLM/qwen-code/issues/4514)). The daemon was started with at least one `--allow-origin ` (or `allowOrigins: [...]` via the embedded API). Cross-origin requests from matched origins receive proper CORS response headers; unmatched origins still get the default 403. The configured pattern list is intentionally NOT echoed in `/capabilities` to avoid leaking the trusted-origin set to unauthenticated readers — browser webui already knows its own origin. | +| `allow_origin` | T2.4 ([#4514](https://github.com/QwenLM/qwen-code/issues/4514)). The daemon was started with at least one `--allow-origin ` (or `allowOrigins: [...]` via the embedded API). Cross-origin requests from matched origins receive proper CORS response headers; unmatched origins still get the default 403. The configured pattern list is intentionally NOT echoed in `/capabilities` to avoid leaking the trusted-origin set to unauthenticated readers — a browser client already knows its own origin. | | `prompt_absolute_deadline` | `--prompt-deadline-ms` / `QWEN_SERVE_PROMPT_DEADLINE_MS` / `ServeOptions.promptDeadlineMs` is set to a positive integer. | | `writer_idle_timeout` | `--writer-idle-timeout-ms` / `QWEN_SERVE_WRITER_IDLE_TIMEOUT_MS` / `ServeOptions.writerIdleTimeoutMs` is set to a positive integer. | | `workspace_settings` | the daemon was created with settings persistence available. | diff --git a/docs/developers/roadmap.md b/docs/developers/roadmap.md index 86fceed853d..56f57908003 100644 --- a/docs/developers/roadmap.md +++ b/docs/developers/roadmap.md @@ -2,13 +2,13 @@ > **Objective**: Catch up with Claude Code's product functionality, continuously refine details, and enhance user experience. -| Category | Phase 1 | Phase 2 | -| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| User Experience | ✅ Terminal UI
✅ Support OpenAI Protocol
✅ Settings
✅ OAuth
✅ Cache Control
✅ Memory
✅ Compress
✅ Theme | Better UI
OnBoarding
LogView
✅ Session
Permission
🔄 Cross-platform Compatibility
✅ Coding Plan
✅ Anthropic Provider
✅ Multimodal Input
✅ Unified WebUI | -| Coding Workflow | ✅ Slash Commands
✅ MCP
✅ PlanMode
✅ TodoWrite
✅ SubAgent
✅ Multi Model
✅ Chat Management
✅ Tools (WebFetch, Bash, TextSearch, FileReadFile, EditFile) | 🔄 Hooks
✅ Skill
✅ Headless Mode
✅ Tools (WebSearch)
✅ LSP Support
✅ Concurrent Runner | -| Building Open Capabilities | ✅ Custom Commands | ✅ QwenCode SDK
✅ Extension System | -| Integrating Community Ecosystem | | ✅ VSCode Plugin
✅ ACP/Zed
✅ GHA | -| Administrative Capabilities | ✅ Stats
✅ Feedback | Costs
Dashboard
✅ User Feedback Dialog | +| Category | Phase 1 | Phase 2 | +| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| User Experience | ✅ Terminal UI
✅ Support OpenAI Protocol
✅ Settings
✅ OAuth
✅ Cache Control
✅ Memory
✅ Compress
✅ Theme | Better UI
OnBoarding
LogView
✅ Session
Permission
🔄 Cross-platform Compatibility
✅ Coding Plan
✅ Anthropic Provider
✅ Multimodal Input
✅ Unified Web Shell | +| Coding Workflow | ✅ Slash Commands
✅ MCP
✅ PlanMode
✅ TodoWrite
✅ SubAgent
✅ Multi Model
✅ Chat Management
✅ Tools (WebFetch, Bash, TextSearch, FileReadFile, EditFile) | 🔄 Hooks
✅ Skill
✅ Headless Mode
✅ Tools (WebSearch)
✅ LSP Support
✅ Concurrent Runner | +| Building Open Capabilities | ✅ Custom Commands | ✅ QwenCode SDK
✅ Extension System | +| Integrating Community Ecosystem | | ✅ VSCode Plugin
✅ ACP/Zed
✅ GHA | +| Administrative Capabilities | ✅ Stats
✅ Feedback | Costs
Dashboard
✅ User Feedback Dialog | > For more details, please see the list below. @@ -19,7 +19,7 @@ | Feature | Version | Description | Category | Phase | | ----------------------- | --------- | ------------------------------------------------------- | ------------------------------- | ----- | | **Coding Plan** | `V0.10.0` | Alibaba Cloud Coding Plan authentication & models | User Experience | 2 | -| Unified WebUI | `V0.9.0` | Shared WebUI component library for VSCode/CLI | User Experience | 2 | +| Unified Web Shell | `V0.9.0` | Shared browser UI for VS Code and `qwen serve` | User Experience | 2 | | Export Chat | `V0.8.0` | Export sessions to Markdown/HTML/JSON/JSONL | User Experience | 2 | | Extension System | `V0.8.0` | Full extension management with slash commands | Building Open Capabilities | 2 | | LSP Support | `V0.7.0` | Experimental LSP service (`--experimental-lsp`) | Coding Workflow | 2 | diff --git a/docs/users/features/commands.md b/docs/users/features/commands.md index 7b17318ef8c..d2d4063e36d 100644 --- a/docs/users/features/commands.md +++ b/docs/users/features/commands.md @@ -34,6 +34,10 @@ These commands help you save, restore, and summarize work progress. | `/export` | Export session history to file | `/export html`, `/export md`, `/export json`, `/export jsonl` | | `/rename` | Rename or tag the current session | `/rename My Feature` or `/tag` | +> [!note] +> +> Opening an HTML export loads the renderer for that exact Qwen Code version from `unpkg.com`. If the version has not been published or the renderer cannot be reached, the file shows a load error. Markdown, JSON, and JSONL exports remain self-contained. + > [!note] > > `/summarize` is an alias for `/compress` (it compresses chat history — a destructive operation). To generate a non-destructive project summary instead, use `/summary`. diff --git a/docs/users/features/followup-suggestions.md b/docs/users/features/followup-suggestions.md index 6393ee84ea7..740c43f0d51 100644 --- a/docs/users/features/followup-suggestions.md +++ b/docs/users/features/followup-suggestions.md @@ -2,7 +2,7 @@ Qwen Code can predict what you want to type next and show it as placeholder text in the input area. This feature uses an LLM call to analyze the conversation context and generate a natural next step suggestion. -This feature works end-to-end in the CLI. In the WebUI, the hook and UI plumbing are available, but host applications must trigger suggestion generation and wire the followup state for suggestions to appear. +This feature works end-to-end in both the CLI and Web Shell. Generation is automatic and server-side: after each completed turn the daemon emits the suggestion on the session stream (on by default; set `ui.enableFollowupSuggestions` to `false` to opt out), and Web Shell's composer already wires the `useDaemonFollowupSuggestion` hook, so suggestions render and accept with no additional host wiring. ## How It Works diff --git a/docs/users/qwen-serve.md b/docs/users/qwen-serve.md index 853ab5324f7..809b53acfa7 100644 --- a/docs/users/qwen-serve.md +++ b/docs/users/qwen-serve.md @@ -418,43 +418,43 @@ Notes: ## CLI flags -| Flag | Default | Purpose | -| --------------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--port ` | `4170` | TCP port. `0` = OS-assigned ephemeral port. | -| `--hostname ` | `127.0.0.1` | Bind interface. Anything beyond loopback requires a token. | -| `--local-control` | `false` | Share the Web Shell on one selected private IPv4 interface with a daemon-owned revocable pairing token, terminal QR code, exact browser origin, and best-effort sleep inhibition. Composes with `--token`, `--allow-origin`, and `--port 0`; conflicts with `--no-web` and non-default `--hostname`. Use `--local-control-address` when multiple LAN candidates are available, and add `--tls-cert` + `--tls-key` for secure-context browser APIs such as voice input. | -| `--local-control-address ` | — | Which LAN IPv4 address to share when the host has more than one candidate. Only needed if `--local-control` reports an ambiguous choice. | -| `--token ` | — | Bearer token. Falls back to `QWEN_SERVER_TOKEN` env var (with leading/trailing whitespace stripped — handy for `$(cat token.txt)`). | -| `--require-auth` | `false` | Refuse to start without a bearer token, even on loopback. Hardens the `127.0.0.1` developer default for shared dev hosts / CI runners / multi-tenant workstations where any local user can hit the listener. Boots only with `--token` or `QWEN_SERVER_TOKEN` set; gates `/health` behind the bearer too. | -| `--enable-session-shell` | `false` | Enable direct session shell execution. Effective with either a configured bearer token or trusted-loopback mode; calls still require a valid session-bound `X-Qwen-Client-Id`. | -| `--tls-cert ` | — | Path to a PEM certificate file. Serve over **HTTPS** instead of HTTP. Must be paired with `--tls-key` (boot fails if only one is given). Unlocks secure-context browser APIs — voice input (`getUserMedia`), WebRTC — over a LAN IP, which browsers otherwise block on plain `http://`. TLS termination only; no auto-generation / ACME. See [HTTPS / TLS](#https--tls-for-mobile--cross-device-access) below. | -| `--tls-key ` | — | Path to a PEM private key file. Must be paired with `--tls-cert`. | -| `--max-sessions ` | `32` | Cap on concurrent live sessions. New `POST /session` requests that would spawn a fresh child return `503` (with `Retry-After: 5`) when the cap is hit; attaches to existing sessions are NOT counted. Set to `0` to disable. Sized for single-user / small-team usage; raise it if your deployment has the RAM/FD headroom (~30–50 MB per session). | -| `--max-total-sessions ` | derived | Optional non-negative integer daemon-wide cap on fresh session creation across all registered workspace runtimes. It applies to new child sessions, session restore, and branch/fork-created sessions; attaching to an existing live session does not consume a slot. Set to `0` for unlimited. When omitted with several startup/restored workspaces, the daemon derives a fixed cap from the per-workspace limit and the startup workspace count; later dynamic registration does not recompute it. | -| `--max-pending-prompts-per-session ` | `5` | Per-session cap on prompts accepted by `POST /session/:id/prompt` but not yet settled, including queued prompts and the active prompt. The bridge rejects overflow synchronously with `503`, `Retry-After: 5`, and `code: "prompt_queue_full"` before returning a `promptId`. Set to `0` to disable. `branchSession` serializes on the same FIFO but does not count against this prompt cap. | -| `--workspace ` | `process.cwd()` | Absolute workspace directory registered by this daemon. Repeat the flag to host multiple workspaces in one process; the first is primary and remains the default when a request omits `cwd`. Relative values are rejected. Session requests whose canonical `cwd` is not registered return `400 workspace_mismatch`. | -| `--memory-project-scope ` | `workspace` | Project-memory partitioning mode. `workspace` (default) keys memory by the exact registered workspace directory so each daemon workspace gets its own isolated memory; `git-root` is the legacy compatibility mode shared by workspaces resolved to the same Git root. Overrides `QWEN_CODE_MEMORY_PROJECT_SCOPE` when provided; a blank env value is treated as unset, while an unrecognized non-empty value is ignored with a one-time warning and retains the legacy `git-root` behavior. The new default does not migrate existing git-root project memory — use an explicit `git-root` scope to read those entries during migration. | -| `--channel ` | — | Experimental daemon-managed channel worker. Repeat the flag to select multiple configured channels, or pass `all` to start every configured channel. `all` cannot be combined with named channels. Selected channel `cwd` values must resolve to a registered workspace; a multi-workspace daemon runs one worker per owning workspace. The worker is owned by `qwen serve`; stop the daemon to stop serve-managed channels. | -| `--max-connections ` | `256` | Listener-level TCP connection cap (`server.maxConnections`). Bounds raw socket count irrespective of session count — slow / phantom SSE clients get rejected at accept time once full. Raise alongside `--max-sessions` if your deployment expects many SSE subscribers per session. | -| `--memory-budget-mb ` | 50% of cgroup/host | Total memory budget in MB for the whole daemon process tree. When unset, derived as 50% of the cgroup limit or host memory; either way the effective value is capped at resolved available memory, and both the configured and effective figures are reported. It does not change how any `qwen --acp` child is sized; the one consumer today is adaptive live-journal growth: one daemon-wide growth pool derived as 5% of the effective budget (capped at `1024` MB; on hosts reporting `insufficientMemory` the pool is 0 and adaptive growth is disabled) is shared by every workspace bridge — see `--max-journal-bytes`. Resolved figures appear under `limits.memory` in `GET /daemon/status`, alongside registered and live child counts and advisory per-child shares under `runtime.memory`. A host too small for the minimum reports `insufficientMemory` rather than being clamped upward; because the derived fraction is 50%, any host under ~2 GB trips this. Pass an explicit `--memory-budget-mb 1024` on such a host to override the derived figure (the flag still requires at least 1024 MB of available memory to clear the warning). Must be an integer in `[1024, 1048576]`. | -| `--memory-pressure-mode ` | `observe` | Whether the daemon turns its own memory reading into a verdict. `observe` (default) reports the pressure level under `runtime.memory.pressure` in `GET /daemon/status` and raises a `daemon_memory_pressure` issue — a `warning`, so the overall `status` leaves `ok` — whenever the level leaves `normal`. `off` still reports every figure, including the level, but raises no issue, so the overall `status` is unchanged; use it while calibrating, or if you alert on the top-level status. The level is the worse of two ratios: RSS against available memory (what the cgroup OOM killer watches) and V8 heap used against this process's heap ceiling. It covers the daemon root process only; compare it against `runtime.memory.children.rssBytes` for the children. Nothing remediates in either mode. One of `off`, `observe`. | -| `--child-heap-mode ` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply — `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` — and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing, and says so on the wire: `maxConcurrentChildren` and `perChildCeilingMb` are both `null` rather than carrying a partition you switched off. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. | -| `--event-ring-size ` | `8000` | Per-session SSE replay ring depth (#3803 §02 target). Sets the backlog available to `GET /session/:id/events` with `Last-Event-ID: N`. Larger = more reconnect headroom at the cost of a few hundred KB extra RAM per session. SDK clients can additionally request a larger per-subscriber backlog cap on a specific subscription via `?maxQueued=N` (range `[16, 2048]`, default 256). Daemons also emit a non-terminal `slow_client_warning` SSE frame at 75% queue fill so clients can drain / reconnect before getting evicted. Pre-flight `caps.features.slow_client_warning`. | -| `--compacted-replay-max-bytes ` | `4194304` | Per-live-session byte cap for the retained replay events in the bounded snapshot returned by `POST /session/:id/load`. The cap applies to `compactedReplay`; the current in-flight `liveJournal` is separately capped by `--max-journal-events` and `--max-journal-bytes` (baseline caps that adaptive growth can raise — see `--max-journal-bytes`). Values must be positive safe integers; invalid values fail at boot, and the hard ceiling is 256 MiB. When older retained replay is dropped, the snapshot begins with `history_truncated`. This does not limit the on-disk transcript. | -| `--max-journal-events ` | `10000` | Per-session baseline cap on replay entries retained in the in-flight `liveJournal` for the current unfinished turn. Consecutive compatible text or thought chunks share an entry, with at most 256 source events per entry; other event boundaries are preserved. When exceeded, the daemon first tries adaptive growth (see `--max-journal-bytes`); if no headroom is granted or the grant does not cover the overshoot, the oldest entries are dropped and a `history_truncated` marker is prepended. The marker's `truncatedEvents` and `retainedEvents` counts describe source events. Must be a positive safe integer. Pinning this flag (or `--max-journal-bytes`) disables adaptive growth. | -| `--max-journal-bytes ` | `8388608` | Per-session baseline byte cap on the in-flight `liveJournal`, accounted from the serialized source events even when compatible chunks share a replay entry. When a turn outgrows the cap, adaptive growth raises the session's caps toward double (up to a per-session hard cap of 256 MiB, limited by the remaining pool headroom) while the growth granted across all of the daemon's live sessions fits in one shared growth pool sized at 5% of the daemon's effective memory budget — the `--memory-budget-mb` value when passed, capped at resolved available memory, otherwise 50% of auto-detected memory (see `--memory-budget-mb`) — capped at `1024` MB; on hosts reporting `insufficientMemory` the pool is 0 and adaptive growth is disabled. Growth happens on demand, and only as far as the pool allows; when it is refused, the pool is exhausted, or a grant does not cover the overshoot, the oldest entries are dropped whole (at least one entry is always kept), so the retained tail can be much smaller than the cap. Pinning this flag (or `--max-journal-events`) disables adaptive growth. Must be a positive safe integer. Defaults to 8 MiB. | -| `--mcp-client-budget ` | — | Positive integer cap on live MCP clients. When `mcp_workspace_pool` is advertised, the cap and transports are shared per workspace runtime; when the tag is absent, the legacy per-session manager enforces it. Combine with `--mcp-budget-mode`. When unset, no accounting-driven enforcement (but `GET /workspace/mcp` still reports `clientCount`). Distinct from claude-code's `MCP_SERVER_CONNECTION_BATCH_SIZE`, which gates startup concurrency rather than total live clients. Pre-flight `caps.features.mcp_guardrails` and `caps.features.mcp_workspace_pool`. | -| `--mcp-budget-mode ` | `warn` / `off` | How `--mcp-client-budget` is enforced. `warn` (default when budget set): no refusal, snapshot's `budgets[0].status` flips to `warning` at ≥75% of budget. `enforce`: connects past the cap are refused, per-server cell shows `disabledReason: 'budget'`, deterministic by `mcpServers` declaration order. `off` (default when budget unset): pure observability. Boot rejects `enforce` without a budget. | -| `--external-tool-guard-mode ` | `off` | Managed ACP external pre-execution policy. `off` makes no provider calls and advertises no capability. `required` fails startup unless a compatible provider completes the v1 handshake, then fails every supported top-level tool invocation closed unless its single prepare request is allowed. | -| `--external-tool-guard-endpoint ` | — | Origin-only loopback HTTP(S) provider URL used in `required` mode, for example `http://127.0.0.1:8787`. Paths, URL credentials, redirects, non-loopback hosts, and proxy routing are not accepted. | -| `--external-tool-guard-timeout-ms ` | `3000` | Integer `100..30000`; applies independently to the startup handshake and each prepare request. | -| `--http-bridge` | `true` | Stage 1 mode: production attempts to preheat one primary `qwen --acp` child for compatibility and retries on first use after failure, while each trusted secondary can start one child on demand. Sessions targeting a runtime multiplex onto its child via ACP `newSession()`; untrusted secondaries cannot start ACP. Stage 2 native in-process becomes available later. | -| `--initialize-timeout-ms ` | `10000` | ACP child request timeout, including the `initialize` handshake (ms). Must be a positive integer up to `2147483647`. Values above the JS timer ceiling (`2^31-1`) are rejected at boot because Node silently compresses them to 1 ms. Cold-container deployments that need extra headroom for child startup can raise this; the same value governs `newSession`, workspace-status polls, and other ACP ext-method deadlines. | -| `--session-restore-timeout-ms ` | `60000` | ACP session load/resume deadline in milliseconds. Must be a positive integer up to `2147483647`; `0` is invalid. If omitted, the default is 60 seconds, raised to an explicitly supplied `--initialize-timeout-ms` when that value is larger; a shorter initialize timeout never lowers the restore budget. The SDK and WebUI add 10 and 15 seconds of client headroom. A timeout returns retryable `504 session_restore_timeout`; it does not imply that the daemon itself exited. | -| `--allow-origin ` | — | T2.4 ([#4514](https://github.com/QwenLM/qwen-code/issues/4514)). Cross-origin allowlist for browser webui clients. Repeatable. Each value is `*` (any origin — boot refuses if no bearer token is configured; `--require-auth` on loopback is recommended so `/health` is also bearer-gated, since it is pre-auth on loopback by default; the Web Shell static assets stay pre-auth in every mode, so pass `--no-web` to remove them) or a canonical URL origin (`://[:]`, no trailing slash / path / userinfo / query). Without a token, HTTP(S) origins must be loopback; remote HTTP(S) origins require a token. Browser extensions retain tokenless access locally. Subdomain wildcards (`https://*.example.com`) are intentionally unsupported — list each subdomain explicitly, or use `*` with a configured token (and `--require-auth` for full hardening). Matched origins receive CORS response headers (`Access-Control-Allow-Origin`, `Vary: Origin`, methods, headers, max-age, and exposed `Retry-After`); unmatched origins still get a 403 with the same envelope as today's wall. `Origin: null` (sandboxed iframes, file:// docs) is always rejected, even under `*`. Pre-flight via `caps.features.allow_origin`. Loopback self-origin hits are unaffected. | -| `--web` / `--no-web` | `true` | Serve the built Web Shell SPA at the daemon root (`GET /`, `/assets/*`, and `GET /session/` document navigations). These entry points are registered **before** the bearer-auth gate — a browser can't attach a token to a ` - - - - - - - - - - - - - - - -
-
-
-

Qwen Code Export

-
-
-
- Session Id - - -
-
- Export Time - - -
-
-
- -
-
- - - - - - diff --git a/integration-tests/terminal-capture/motivation.md b/integration-tests/terminal-capture/motivation.md index 3d004ddeee5..77b08140160 100644 --- a/integration-tests/terminal-capture/motivation.md +++ b/integration-tests/terminal-capture/motivation.md @@ -7,7 +7,7 @@ | Unit Tests | Vitest + ink-testing-library | Ink components, Core logic, utilities | Mature, extensive `.test.ts` / `.test.tsx` | | Integration Tests | Vitest + TestRig / SDKTestHelper | CLI E2E, SDK multi-turn, MCP, auth | Mature, supports none/docker/podman sandboxes | | Terminal UI Snapshots | `toMatchSnapshot()` + ink-testing-library | Ink component render output (ANSI) | Exists, covers Footer, InputPrompt, MarkdownDisplay, etc. | -| Web UI Regression | Chromatic + Storybook | `packages/webui` components | Exists, but only covers Web UI | +| Web Shell Regression | Playwright visual tests | `packages/web-shell` components | Exists for browser UI scenarios | | **Terminal UI Visual** | **terminal-capture** | CLI terminal real rendering screenshots | ✅ Implemented | ## 2. Problems Solved by terminal-capture @@ -113,7 +113,7 @@ scenarios/screenshots/ │ Unit Tests (Vitest) │ ← Function/Component level │ Text Snapshots (ink-testing-lib) │ ← ANSI string comparison │ Integration Tests (TestRig/SDK) │ ← E2E functionality -│ Web UI Regression (Chromatic) │ ← Only covers webui +│ Web Shell Regression (Playwright) │ ← Covers browser UI scenarios ├─────────────────────────────────────┤ │ terminal-capture │ ← Terminal UI visual layer │ (xterm.js + Playwright) │ Fills the gap @@ -124,4 +124,4 @@ scenarios/screenshots/ 1. **Visual Regression** — Integrate Playwright `toHaveScreenshot()` for pixel-level baseline comparison, CI auto-detects terminal UI changes 2. **PR Workflow Integration** — Drive Agent via Cursor Skill to auto-checkout branch → build → screenshot → attach to review comment -3. **Complement to Chromatic** — Chromatic covers Web UI, terminal-capture covers CLI terminal UI +3. **Complement to Web Shell visual tests** — Playwright visual tests cover the Web Shell browser UI, terminal-capture covers CLI terminal UI diff --git a/integration-tests/tsconfig.json b/integration-tests/tsconfig.json index d1383fb86e2..3836b355d5f 100644 --- a/integration-tests/tsconfig.json +++ b/integration-tests/tsconfig.json @@ -127,7 +127,7 @@ "@qwen-code/acp-bridge/workspacePaths": [ "../packages/acp-bridge/src/workspacePaths.ts" ], - // qwen-serve-webui-live-journal-recovery.test.ts imports Web Shell's + // qwen-serve-web-shell-live-journal-recovery.test.ts imports Web Shell's // daemon bindings; map to source so typecheck does not depend on dist. "@qwen-code/web-shell/daemon-react-sdk": [ "../packages/web-shell/client/daemon-react-sdk.ts" diff --git a/package-lock.json b/package-lock.json index 97caffb8d63..fb49659f1ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "msw": "^2.10.4", "npm-run-all": "^4.1.5", "patch-package": "^8.0.1", + "playwright": "^1.57.0", "prettier": "^3.5.3", "react-devtools-core": "^6.1.5", "semver": "^7.7.2", @@ -195,13 +196,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" - }, "node_modules/@agentclientprotocol/sdk": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-0.14.1.tgz", @@ -1136,27 +1130,6 @@ "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", "license": "Apache-2.0" }, - "node_modules/@chromatic-com/storybook": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-5.0.0.tgz", - "integrity": "sha512-8wUsqL8kg6R5ue8XNE7Jv/iD1SuE4+6EXMIGIuE+T2loBITEACLfC3V8W44NJviCLusZRMWbzICddz0nU0bFaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@neoconfetti/react": "^1.0.0", - "chromatic": "^13.3.4", - "filesize": "^10.0.12", - "jsonfile": "^6.1.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=20.0.0", - "yarn": ">=1.22.18" - }, - "peerDependencies": { - "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0" - } - }, "node_modules/@codemirror/autocomplete": { "version": "6.20.3", "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", @@ -1559,18 +1532,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/runtime": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", @@ -1581,17 +1542,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.6", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.6.tgz", @@ -3205,132 +3155,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@joshwooding/vite-plugin-react-docgen-typescript/-/vite-plugin-react-docgen-typescript-0.6.3.tgz", - "integrity": "sha512-9TGZuAX+liGkNKkwuo3FYJu7gHWT0vkBcf7GkOe7s7fmC19XwH/4u5u7sDIFrMooe558ORcmuBvBz7Ur5PlbHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^11.1.0", - "react-docgen-typescript": "^2.2.2" - }, - "peerDependencies": { - "typescript": ">= 4.3.x", - "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/glob": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", - "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "foreground-child": "^3.3.1", - "jackspeak": "^4.1.1", - "minimatch": "^10.1.1", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/jackspeak": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", - "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/lru-cache": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", - "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.8" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@joshwooding/vite-plugin-react-docgen-typescript/node_modules/path-scurry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", - "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -3648,24 +3472,6 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, - "node_modules/@mdx-js/react": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", - "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mdx": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "@types/react": ">=16", - "react": ">=16" - } - }, "node_modules/@mermaid-js/parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.2.1.tgz", @@ -3675,169 +3481,6 @@ "@chevrotain/types": "~11.1.2" } }, - "node_modules/@microsoft/api-extractor": { - "version": "7.59.0", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-7.59.0.tgz", - "integrity": "sha512-KDYV8kSVjmG8JJWVg1f1aKxteNG1IQ44D07Rjhlcci8wlqrSFNhUfgv7dJhwT0W2h3NDIL5Vz2f+/DfO4OpDHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/api-extractor-model": "7.33.11", - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.1", - "@rushstack/node-core-library": "5.24.0", - "@rushstack/rig-package": "0.7.3", - "@rushstack/terminal": "0.24.3", - "@rushstack/ts-command-line": "5.3.13", - "diff": "~8.0.2", - "minimatch": "10.2.3", - "resolve": "~1.22.1", - "semver": "~7.7.4", - "source-map": "~0.6.1", - "typescript": "5.9.3" - }, - "bin": { - "api-extractor": "bin/api-extractor" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "node_modules/@microsoft/api-extractor-model": { - "version": "7.33.11", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor-model/-/api-extractor-model-7.33.11.tgz", - "integrity": "sha512-iDu1AtuRC2Z8XVs2SieZieVavF1FXPBX1C9pMexJh8Dx/Dd/3ZZ4nRQp/PU2Vk2rUHWuBz1wOyL4DcuhVnBXwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "~0.16.0", - "@microsoft/tsdoc-config": "~0.18.1", - "@rushstack/node-core-library": "5.24.0" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/diff": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", - "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", - "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@microsoft/tsdoc": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz", - "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@microsoft/tsdoc-config": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.18.1.tgz", - "integrity": "sha512-9brPoVdfN9k9g0dcWkFeA7IH9bbcttzDJlXvkf8b2OBzd5MueR1V2wkKBL0abn0otvmkHJC6aapBOTJDDeMCZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/tsdoc": "0.16.0", - "ajv": "~8.18.0", - "jju": "~1.4.0", - "resolve": "~1.22.2" - } - }, - "node_modules/@microsoft/tsdoc-config/node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/@mixmark-io/domino": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", @@ -4057,35 +3700,6 @@ "node": "^22.20 || ^24.12 || >=25" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", - "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.3" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=23.5.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" - } - }, - "node_modules/@neoconfetti/react": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz", - "integrity": "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A==", - "dev": true, - "license": "MIT" - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -4567,674 +4181,6 @@ "win32" ] }, - "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.127.0.tgz", - "integrity": "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.127.0.tgz", - "integrity": "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.127.0.tgz", - "integrity": "sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.127.0.tgz", - "integrity": "sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.127.0.tgz", - "integrity": "sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.127.0.tgz", - "integrity": "sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.127.0.tgz", - "integrity": "sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.127.0.tgz", - "integrity": "sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.127.0.tgz", - "integrity": "sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.127.0.tgz", - "integrity": "sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.127.0.tgz", - "integrity": "sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.127.0.tgz", - "integrity": "sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.127.0.tgz", - "integrity": "sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.127.0.tgz", - "integrity": "sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.127.0.tgz", - "integrity": "sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.127.0.tgz", - "integrity": "sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.127.0.tgz", - "integrity": "sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.4" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.127.0.tgz", - "integrity": "sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.127.0.tgz", - "integrity": "sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.127.0.tgz", - "integrity": "sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.21.2.tgz", - "integrity": "sha512-xQoCRv+gKax9KTdwdaQNnAFOai8neay7g3jExDIORzhbrejwGSJaZNTdOJHR5ziLg2joMxOCMOFMo4zuxza2uQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.21.2.tgz", - "integrity": "sha512-HF5oiE2L05yInPYCFD/4uxSrEZW4SuIfn99Y6L1xnJnzl066JR+MJs2rIdstw8A2MPlAKH+13dpFPNycjqzvGg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.21.2.tgz", - "integrity": "sha512-UX4u49CVCAD8QZNELaW8eMGgMAGwFWYEPbvNsh+3r/gs4NX3KfpiACMVwRQT0EuH3uat9hM5Zl+Ppm9pJD8tgg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.21.2.tgz", - "integrity": "sha512-J9xPx7YBkrRmJ+xl561ztnMWEc1aOyjEIxBiGX1dVb3u7bGSnfObfcZk+Pd+uM0HZAPNsQ1xvD8j52A/uOSqNQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.21.2.tgz", - "integrity": "sha512-fRlt7OvSaQkWj6+EDTVxawVxOlqJB2QSnBfkeCyK4RTvsGctbw3BiH2Tb7DzMs7bikc4BRBpvWP5zF9K8b54Zg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.21.2.tgz", - "integrity": "sha512-gK+vPUcPQITkGwBKpZGrcDHSlU6eDGl7AQacxS2CEKAZIBHWkOVFeJwLZ4tYnA1acJqRM5lt7yYwPCVqGHIJ7A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.21.2.tgz", - "integrity": "sha512-L/Rgas7SrOKy/z7IH+HxSFRqVO4PuLDKLEGKvnhoCBBq3UJ0YzGBou3qMzaAGgkJwsIvX2pBF+7ojzfUhLiZxQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.21.2.tgz", - "integrity": "sha512-CUEYvlX1Fk7E9kUMzuswru1J9HLxMwnpDeQGjQuI4ZH+iNCoa2X9T+pvyzrbsgl7WnIeTFTlNlHsRfVdf5g9/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.21.2.tgz", - "integrity": "sha512-ViN1ZibQyxwC67GpoP33oo+S9UyUnkog13vzQb9+v9bCNvrVzJuk0MRdacjtv/9xfRMVF/eqHFyl8YOeykI10Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.21.2.tgz", - "integrity": "sha512-CU1sCqWnhGqYD5I1HedHk5pujrn7ssDkNB/AEQd/pd3D/EojVSgJUlpbafwIbyhia3PgIfkvdFpRPWSALzVumA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.21.2.tgz", - "integrity": "sha512-jWVyZtIHca4Gb96x7dag+y69vlei7ffjrsveLkmf2ZhqEAz6ZSBnY1GWvgZUaZlwZP62A3xD092BW97Q5VGc+g==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.21.2.tgz", - "integrity": "sha512-LF29obFqNgBUgDX7rmUK7M4D0JQG5LxhYzn3xXmECcHU9aQAdWG7NiY052qybtesEdwHQXKNTWYQ7mTsybNvWg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.21.2.tgz", - "integrity": "sha512-+NYcm+cCHBbtdQQ3A4phQTSuVRYnNHz7wrl9XRAPEovcdoqi0mb1K5ZOl+jN54ZD+q1zz3V0vltbFJmzecJKmw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.21.2.tgz", - "integrity": "sha512-UQqZDdG2r2HhAOsZEgufkIWHPQ886IUyuJQkoZByvzhW8j51R4UNzGBJFkTiTnLhQnggwJRdJgFWK4uY6ZVIMw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.21.2.tgz", - "integrity": "sha512-3Q9PMRjalWkT6NZ4jfujuqTCFwoWErg3y3BnOgb544B8IMw4PktiwWOigMfOHNRLMghZeJ7hpfpZf4CP7rV7Og==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.21.2.tgz", - "integrity": "sha512-Eljeq3ndtyKhM+Es8LITi4Zl2htzuRZcrPMF3kMCsrILztvU6AjZ3FuEhHHKobPUB9rMpzLpJP5bDqXI7r+iog==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.21.2.tgz", - "integrity": "sha512-HGDbNsIywqc4LxU38+CTJNnB/6BF7rheWOJ259b4eE0aEnelYblC8x+1tEd63bp31fYne63RQz9Jb4yVnC7Yig==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.0", - "@emnapi/runtime": "1.11.0", - "@napi-rs/wasm-runtime": "^1.1.5" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", - "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/runtime": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", - "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.21.2.tgz", - "integrity": "sha512-iWx25CBEgH49iE9q5coEGI/jb1jl5kkCY9z6U5Og67xCkQ/WFMDc2J5U78+AE91SUxM2NqSLhJC8/PLfWnImww==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.21.2.tgz", - "integrity": "sha512-VPoCAhKvCQTlG7vxqaBXcmuvbh77BfnGXj8g0pbvVXpm1F/R8rDVwqIWcEbMzrI1JvJlm8v7T9uMVQb6UctMRg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", @@ -5323,7 +4269,9 @@ "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", @@ -5478,10 +4426,6 @@ "resolved": "packages/web-templates", "link": true }, - "node_modules/@qwen-code/webui": { - "resolved": "packages/webui", - "link": true - }, "node_modules/@radix-ui/number": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", @@ -6984,49 +5928,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/pluginutils/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.5", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.5.tgz", @@ -7384,177 +6285,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rushstack/node-core-library": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/@rushstack/node-core-library/-/node-core-library-5.24.0.tgz", - "integrity": "sha512-g/Z47ZwARn/VkTnHcyJslrR26erRf1G5JbqPlfGZGBCrOcrEt8fTQXXDVhUDDNl/g/v3TXI1dNiAAT5FEks8BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "~8.20.0", - "ajv-draft-04": "~1.0.0", - "ajv-formats": "~3.0.1", - "fs-extra": "~11.3.0", - "import-lazy": "~4.0.0", - "jju": "~1.4.0", - "resolve": "~1.22.1", - "semver": "~7.7.4" - }, - "engines": { - "node": ">=20.9.0" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/node-core-library/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@rushstack/node-core-library/node_modules/ajv-draft-04": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", - "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^8.5.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/@rushstack/node-core-library/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rushstack/node-core-library/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@rushstack/problem-matcher": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@rushstack/problem-matcher/-/problem-matcher-0.2.1.tgz", - "integrity": "sha512-gulfhBs6n+I5b7DvjKRfhMGyUejtSgOHTclF/eONr8hcgF1APEDjhxIsfdUYYMzC3rvLwGluqLjbwCFZ8nxrog==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/rig-package": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@rushstack/rig-package/-/rig-package-0.7.3.tgz", - "integrity": "sha512-aAA518n6wxxjCfnTAOjQnm7ngNE0FVHxHAw2pxKlIhxrMn0XQjGcXKF0oKWpjBgJOmsaJpVob/v+zr3zxgPWuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jju": "~1.4.0", - "resolve": "~1.22.1" - } - }, - "node_modules/@rushstack/terminal": { - "version": "0.24.3", - "resolved": "https://registry.npmjs.org/@rushstack/terminal/-/terminal-0.24.3.tgz", - "integrity": "sha512-KxphDhPGC4xDrKg8O4yrWCEpPMi87aJc1iycXqMVh1dLgV0M34hiH9ZTgWjBRmcrT/OIHoOeWf48npcQFzgp+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rushstack/node-core-library": "5.24.0", - "@rushstack/problem-matcher": "0.2.1", - "supports-color": "~8.1.1" - }, - "engines": { - "node": ">=20.9.0" - }, - "peerDependencies": { - "@types/node": "*" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - } - } - }, - "node_modules/@rushstack/terminal/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/@rushstack/ts-command-line": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.13.tgz", - "integrity": "sha512-+jNbxhh8CkrZYxMk9X3GRYM2+Myr1xCCj+eHbJ9Vp+PCdNHS0TUl5QQFT6UbM/aTFRCzs5jiBTKYzBRpe5uYbQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rushstack/terminal": "0.24.3", - "@types/argparse": "1.0.38", - "argparse": "~1.0.9", - "string-argv": "~0.3.1" - }, - "engines": { - "node": ">=20.9.0" - } - }, - "node_modules/@rushstack/ts-command-line/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -7807,268 +6537,6 @@ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "license": "MIT" }, - "node_modules/@storybook/addon-a11y": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-10.2.0.tgz", - "integrity": "sha512-PJVvEr6KpuOvCr1megfp39RNvFSut6XmFxaiDKtf8kxYbD8tMYL2n/9xFcPIvozJCO4zRmug50X+OIoh0GsSGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "axe-core": "^4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.2.0" - } - }, - "node_modules/@storybook/addon-docs": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.2.0.tgz", - "integrity": "sha512-2iVQmbgguRWQAxJ7HFje7PQFHZIDCYjFNt9zKLaF8NmCS3OI1qVON5Tb/KH30f9epa5Y42OarPEewJE9J+Tw9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "10.2.0", - "@storybook/icons": "^2.0.1", - "@storybook/react-dom-shim": "10.2.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.2.0" - } - }, - "node_modules/@storybook/addon-onboarding": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/addon-onboarding/-/addon-onboarding-10.2.0.tgz", - "integrity": "sha512-6JEgceYEEER9vVjmjiT1AKROMiwzZkSo+MN76wZMKayLX9fA8RIjrRGF3C5CNOVadbcbbvgPmwcLZMgD+0VZlg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.2.0" - } - }, - "node_modules/@storybook/addon-vitest": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/addon-vitest/-/addon-vitest-10.2.0.tgz", - "integrity": "sha512-MNGRhwC5pIEWfNbMxD6pQTqYWq8YwBdRsXkFX00rk3y88YV3w9zg/pHHk6v/+fGnrM9L/upwkIOvlaNMWn8uHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@vitest/browser": "^3.0.0 || ^4.0.0", - "@vitest/browser-playwright": "^4.0.0", - "@vitest/runner": "^3.0.0 || ^4.0.0", - "storybook": "^10.2.0", - "vitest": "^3.0.0 || ^4.0.0" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/runner": { - "optional": true - }, - "vitest": { - "optional": true - } - } - }, - "node_modules/@storybook/builder-vite": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.0.tgz", - "integrity": "sha512-S1+62ipGmQzGPZfcbgNqpbrCezsqkvbhj+MBbQ6VS46b2HcPjm4H8V6FzGly0Ja2pSgu8gT1BQ5N+3yOG8UNTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/csf-plugin": "10.2.0", - "@vitest/mocker": "3.2.4", - "ts-dedent": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "storybook": "^10.2.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@storybook/csf-plugin": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.0.tgz", - "integrity": "sha512-Cty+tZ0r1AZhwBBzqI4RyCpMVGt9wHGTtG4YCRUuNgVFO1MnjaFBHKRT+oT7md28+BWYjFz4Qtpge/fcWANJ0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "unplugin": "^2.3.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "esbuild": "*", - "rollup": "*", - "storybook": "^10.2.0", - "vite": "*", - "webpack": "*" - }, - "peerDependenciesMeta": { - "esbuild": { - "optional": true - }, - "rollup": { - "optional": true - }, - "vite": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/@storybook/global": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", - "integrity": "sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@storybook/icons": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@storybook/icons/-/icons-2.1.0.tgz", - "integrity": "sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@storybook/react": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.0.tgz", - "integrity": "sha512-ciJlh1UGm0GBXQgqrYFeLmiix+KGFB3v37OnAYjGghPS9OP6S99XyshxY/6p0sMOYtS+eWS2gPsOKNXNnLDGYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.2.0", - "react-docgen": "^8.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.0", - "typescript": ">= 4.9.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@storybook/react-dom-shim": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.0.tgz", - "integrity": "sha512-PEQofiruE6dBGzUQPXZZREbuh1t62uRBWoUPRFNAZi79zddlk7+b9qu08VV9cvf68mwOqqT1+VJ1P+3ClD2ZVw==", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.0" - } - }, - "node_modules/@storybook/react-vite": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.0.tgz", - "integrity": "sha512-tIXRfrA+wREFuA+bIJccMCV1YVFdACENcSnSlnB5Be3m8ynMHukOz6ObX9jI5WsWZnqrk0/eHyiYJyVhpY9rhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", - "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.2.0", - "@storybook/react": "10.2.0", - "empathic": "^2.0.0", - "magic-string": "^0.30.0", - "react-docgen": "^8.0.0", - "resolve": "^1.22.8", - "tsconfig-paths": "^4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.2.0", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/@storybook/react-vite/node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@storybook/react-vite/node_modules/tsconfig-paths": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "json5": "^2.2.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/@stylistic/eslint-plugin": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-3.1.0.tgz", @@ -8713,39 +7181,14 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, "node_modules/@testing-library/user-event": { "version": "14.6.1", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12", "npm": ">=6" @@ -8908,17 +7351,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@types/archiver": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.3.tgz", @@ -8929,13 +7361,6 @@ "@types/readdir-glob": "*" } }, - "node_modules/@types/argparse": { - "version": "1.0.38", - "resolved": "https://registry.npmjs.org/@types/argparse/-/argparse-1.0.38.tgz", - "integrity": "sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/aria-query": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", @@ -9346,13 +7771,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/doctrine": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", - "integrity": "sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/dotenv": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/@types/dotenv/-/dotenv-6.1.1.tgz", @@ -9575,13 +7993,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/methods": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", @@ -9729,13 +8140,6 @@ "@types/node": "*" } }, - "node_modules/@types/resolve": { - "version": "1.20.6", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", - "integrity": "sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", @@ -10237,6 +8641,8 @@ "integrity": "sha512-gIzazUkbQfv6T1rJHOLhMMKQnplKAvvQ7QNGaFwI6oCsp4z2aSDZCojGpX3QX3+MYsvJdyy/8BRIYVEbAkMkEA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@testing-library/dom": "^10.4.0", "@testing-library/user-event": "^14.6.1", @@ -10273,6 +8679,8 @@ "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vitest/spy": "3.2.7", "estree-walker": "^3.0.3", @@ -10300,6 +8708,8 @@ "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "tinyrainbow": "^2.0.0" }, @@ -10313,6 +8723,8 @@ "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "tinyspy": "^4.0.3" }, @@ -10326,6 +8738,8 @@ "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@vitest/pretty-format": "3.2.7", "loupe": "^3.1.4", @@ -10392,63 +8806,6 @@ } } }, - "node_modules/@vitest/expect": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", - "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/chai": "^5.2.2", - "@vitest/spy": "3.2.4", - "@vitest/utils": "3.2.4", - "chai": "^5.2.0", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", - "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "3.2.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.17" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", - "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vitest/runner": { "version": "3.2.7", "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", @@ -10520,34 +8877,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/spy": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", - "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyspy": "^4.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", - "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "3.2.4", - "loupe": "^3.1.4", - "tinyrainbow": "^2.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, "node_modules/@vscode/vsce": { "version": "3.9.2", "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-3.9.2.tgz", @@ -10883,76 +9212,6 @@ "node": ">=12" } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", - "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.27", - "entities": "^7.0.0", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@vue/compiler-core/node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", - "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.27", - "@vue/shared": "3.5.27" - } - }, - "node_modules/@vue/compiler-vue2": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", - "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.27", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", - "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webcontainer/env": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@webcontainer/env/-/env-1.1.1.tgz", - "integrity": "sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==", - "dev": true, - "license": "MIT" - }, "node_modules/@wecom/aibot-node-sdk": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@wecom/aibot-node-sdk/-/aibot-node-sdk-1.0.7.tgz", @@ -11140,13 +9399,6 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, - "node_modules/alien-signals": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-0.4.14.tgz", - "integrity": "sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==", - "dev": true, - "license": "MIT" - }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -11730,16 +9982,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axe-core": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", - "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", - "dev": true, - "license": "MPL-2.0", - "engines": { - "node": ">=4" - } - }, "node_modules/axios": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", @@ -12480,30 +10722,6 @@ "node": ">=18" } }, - "node_modules/chromatic": { - "version": "13.3.5", - "resolved": "https://registry.npmjs.org/chromatic/-/chromatic-13.3.5.tgz", - "integrity": "sha512-MzPhxpl838qJUo0A55osCF2ifwPbjcIPeElr1d4SHcjnHoIcg7l1syJDrAYK/a+PcCBrOGi06jPNpQAln5hWgw==", - "dev": true, - "license": "MIT", - "bin": { - "chroma": "dist/bin.js", - "chromatic": "dist/bin.js", - "chromatic-cli": "dist/bin.js" - }, - "peerDependencies": { - "@chromatic-com/cypress": "^0.*.* || ^1.0.0", - "@chromatic-com/playwright": "^0.*.* || ^1.0.0" - }, - "peerDependenciesMeta": { - "@chromatic-com/cypress": { - "optional": true - }, - "@chromatic-com/playwright": { - "optional": true - } - } - }, "node_modules/ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -12828,13 +11046,6 @@ "node": ">=18" } }, - "node_modules/compare-versions": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", - "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", - "dev": true, - "license": "MIT" - }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -13311,13 +11522,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -13942,13 +12146,6 @@ "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", "license": "MIT" }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, "node_modules/debounce-fn": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", @@ -14503,16 +12700,6 @@ "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", "license": "MIT" }, - "node_modules/empathic": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", - "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -15245,187 +13432,6 @@ "semver": "bin/semver.js" } }, - "node_modules/eslint-plugin-storybook": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.2.0.tgz", - "integrity": "sha512-OtQJ153FOusr8bIMzccjkfMFJEex/3NFx0iXZ+UaeQ0WXearQ+37EGgBay3onkFElyu8AySggq/fdTknPAEvPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/utils": "^8.48.0" - }, - "peerDependencies": { - "eslint": ">=8", - "storybook": "^10.2.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.53.1", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/eslint-plugin-storybook/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/eslint-plugin/node_modules/requireindex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.1.0.tgz", @@ -15784,13 +13790,6 @@ "express": ">= 4.11" } }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "dev": true, - "license": "MIT" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -16010,16 +14009,6 @@ "node": ">=16.0.0" } }, - "node_modules/filesize": { - "version": "10.1.6", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", - "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">= 10.4.0" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -17165,16 +15154,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/headers-polyfill": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz", @@ -17411,16 +15390,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/import-meta-resolve": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", @@ -18652,13 +16621,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/jju": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.4.0.tgz", - "integrity": "sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==", - "dev": true, - "license": "MIT" - }, "node_modules/jose": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", @@ -19029,13 +16991,6 @@ "node": ">=6" } }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "dev": true, - "license": "MIT" - }, "node_modules/layout-base": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", @@ -19162,6 +17117,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19183,6 +17139,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19204,6 +17161,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19225,6 +17183,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19246,6 +17205,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19267,6 +17227,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19288,6 +17249,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19309,6 +17271,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19330,6 +17293,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19351,6 +17315,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -19372,6 +17337,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">= 12.0.0" }, @@ -21149,16 +19115,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -21384,6 +19340,8 @@ "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=10" } @@ -22439,75 +20397,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oxc-parser": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.127.0.tgz", - "integrity": "sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "^0.127.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.127.0", - "@oxc-parser/binding-android-arm64": "0.127.0", - "@oxc-parser/binding-darwin-arm64": "0.127.0", - "@oxc-parser/binding-darwin-x64": "0.127.0", - "@oxc-parser/binding-freebsd-x64": "0.127.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.127.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.127.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.127.0", - "@oxc-parser/binding-linux-arm64-musl": "0.127.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.127.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.127.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.127.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.127.0", - "@oxc-parser/binding-linux-x64-gnu": "0.127.0", - "@oxc-parser/binding-linux-x64-musl": "0.127.0", - "@oxc-parser/binding-openharmony-arm64": "0.127.0", - "@oxc-parser/binding-wasm32-wasi": "0.127.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.127.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.127.0", - "@oxc-parser/binding-win32-x64-msvc": "0.127.0" - } - }, - "node_modules/oxc-resolver": { - "version": "11.21.2", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.21.2.tgz", - "integrity": "sha512-w5tLwYN3Zo24w5EeWJjJWZOwhYqTtC8PS2B1tIt7BZUuqTIcU07sQValbDw+rq7+AuAGzOHklgK+ifsy4lpXfw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - }, - "optionalDependencies": { - "@oxc-resolver/binding-android-arm-eabi": "11.21.2", - "@oxc-resolver/binding-android-arm64": "11.21.2", - "@oxc-resolver/binding-darwin-arm64": "11.21.2", - "@oxc-resolver/binding-darwin-x64": "11.21.2", - "@oxc-resolver/binding-freebsd-x64": "11.21.2", - "@oxc-resolver/binding-linux-arm-gnueabihf": "11.21.2", - "@oxc-resolver/binding-linux-arm-musleabihf": "11.21.2", - "@oxc-resolver/binding-linux-arm64-gnu": "11.21.2", - "@oxc-resolver/binding-linux-arm64-musl": "11.21.2", - "@oxc-resolver/binding-linux-ppc64-gnu": "11.21.2", - "@oxc-resolver/binding-linux-riscv64-gnu": "11.21.2", - "@oxc-resolver/binding-linux-riscv64-musl": "11.21.2", - "@oxc-resolver/binding-linux-s390x-gnu": "11.21.2", - "@oxc-resolver/binding-linux-x64-gnu": "11.21.2", - "@oxc-resolver/binding-linux-x64-musl": "11.21.2", - "@oxc-resolver/binding-openharmony-arm64": "11.21.2", - "@oxc-resolver/binding-wasm32-wasi": "11.21.2", - "@oxc-resolver/binding-win32-arm64-msvc": "11.21.2", - "@oxc-resolver/binding-win32-x64-msvc": "11.21.2" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -23769,23 +21658,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", @@ -24045,51 +21917,6 @@ } } }, - "node_modules/react-docgen": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-8.0.2.tgz", - "integrity": "sha512-+NRMYs2DyTP4/tqWz371Oo50JqmWltR1h2gcdgUMAWZJIAvrd0/SqlCfx7tpzpl/s36rzw6qH2MjoNrxtRNYhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.2", - "@types/babel__core": "^7.20.5", - "@types/babel__traverse": "^7.20.7", - "@types/doctrine": "^0.0.9", - "@types/resolve": "^1.20.2", - "doctrine": "^3.0.0", - "resolve": "^1.22.1", - "strip-indent": "^4.0.0" - }, - "engines": { - "node": "^20.9.0 || >=22" - } - }, - "node_modules/react-docgen-typescript": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.4.0.tgz", - "integrity": "sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">= 4.3.x" - } - }, - "node_modules/react-docgen/node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/react-dom": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", @@ -24421,43 +22248,6 @@ "node": ">= 4" } }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/redent/node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/redent/node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -25699,6 +23489,8 @@ "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", @@ -25873,13 +23665,6 @@ "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "license": "CC0-1.0" }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", @@ -25964,55 +23749,6 @@ "node": ">= 0.4" } }, - "node_modules/storybook": { - "version": "10.5.10", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.5.10.tgz", - "integrity": "sha512-Rz8k9ejFHsi7lbtJTaxZlhCUz4GkbJIKEoKDjXeLfr/ZhXip73E6keKxW0KH8iGeKiCqHAbJCV4YIQrxTOLiig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.2", - "@testing-library/dom": "^10.4.1", - "@testing-library/jest-dom": "6.9.1", - "@testing-library/user-event": "^14.6.1", - "@vitest/expect": "3.2.4", - "@vitest/spy": "3.2.4", - "@webcontainer/env": "^1.1.1", - "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0 || ^0.28.0", - "jsonc-parser": "^3.3.1", - "open": "^10.2.0", - "oxc-parser": "^0.127.0", - "oxc-resolver": "11.21.2", - "recast": "^0.23.5", - "semver": "^7.7.3", - "use-sync-external-store": "^1.5.0", - "ws": "^8.21.1" - }, - "bin": { - "storybook": "dist/bin/dispatcher.js" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/storybook" - }, - "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "prettier": "^2 || ^3", - "vite-plus": "^0.1.15 || ^0.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "prettier": { - "optional": true - }, - "vite-plus": { - "optional": true - } - } - }, "node_modules/streamx": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", @@ -26308,19 +24044,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-indent": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", - "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -27170,6 +24893,8 @@ "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=6" } @@ -27785,35 +25510,6 @@ "node": ">= 0.8" } }, - "node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/update-browserslist-db": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", @@ -27916,16 +25612,6 @@ } } }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -28842,13 +26528,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vscode-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", - "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", - "dev": true, - "license": "MIT" - }, "node_modules/w3c-keyname": { "version": "2.2.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", @@ -28903,13 +26582,6 @@ "node": ">=12" } }, - "node_modules/webpack-virtual-modules": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", - "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", - "dev": true, - "license": "MIT" - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", @@ -34599,684 +32271,6 @@ "optional": true } } - }, - "packages/webui": { - "name": "@qwen-code/webui", - "version": "0.23.0", - "license": "MIT", - "dependencies": { - "@qwen-code/sdk": "file:../sdk-typescript", - "markdown-it": "^14.1.0" - }, - "devDependencies": { - "@chromatic-com/storybook": "^5.0.0", - "@storybook/addon-a11y": "^10.1.11", - "@storybook/addon-docs": "^10.1.11", - "@storybook/addon-onboarding": "^10.1.11", - "@storybook/addon-vitest": "^10.1.11", - "@storybook/react-vite": "^10.1.11", - "@types/markdown-it": "^14.1.2", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.2.0", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4", - "autoprefixer": "^10.4.0", - "eslint-plugin-storybook": "^10.1.11", - "playwright": "^1.57.0", - "postcss": "^8.4.0", - "storybook": "^10.1.11", - "tailwindcss": "^3.4.0", - "typescript": "^5.0.0", - "vite": "^5.0.0", - "vite-plugin-dts": "^4.5.4", - "vitest": "^3.2.4" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "packages/webui/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/webui/node_modules/@volar/language-core": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", - "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/source-map": "2.4.28" - } - }, - "packages/webui/node_modules/@volar/source-map": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", - "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", - "dev": true, - "license": "MIT" - }, - "packages/webui/node_modules/@volar/typescript": { - "version": "2.4.28", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", - "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "2.4.28", - "path-browserify": "^1.0.1", - "vscode-uri": "^3.0.8" - } - }, - "packages/webui/node_modules/@vue/language-core": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.0.tgz", - "integrity": "sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@volar/language-core": "~2.4.11", - "@vue/compiler-dom": "^3.5.0", - "@vue/compiler-vue2": "^2.7.16", - "@vue/shared": "^3.5.0", - "alien-signals": "^0.4.9", - "minimatch": "^9.0.3", - "muggle-string": "^0.4.1", - "path-browserify": "^1.0.1" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "packages/webui/node_modules/@vue/language-core/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/webui/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "packages/webui/node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "dev": true, - "license": "MIT" - }, - "packages/webui/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "packages/webui/node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "packages/webui/node_modules/muggle-string": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", - "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", - "dev": true, - "license": "MIT" - }, - "packages/webui/node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "packages/webui/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "packages/webui/node_modules/vite-plugin-dts": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/vite-plugin-dts/-/vite-plugin-dts-4.5.4.tgz", - "integrity": "sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@microsoft/api-extractor": "^7.50.1", - "@rollup/pluginutils": "^5.1.4", - "@volar/typescript": "^2.4.11", - "@vue/language-core": "2.2.0", - "compare-versions": "^6.1.1", - "debug": "^4.4.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.0.0", - "magic-string": "^0.30.17" - }, - "peerDependencies": { - "typescript": "*", - "vite": "*" - }, - "peerDependenciesMeta": { - "vite": { - "optional": true - } - } } } } diff --git a/package.json b/package.json index 75906d5ec12..ff35fce199e 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "smoke:sdk:python": "python3 packages/sdk-python/scripts/smoke_real.py", "build:sdk:python": "python3 -m build packages/sdk-python", "check-i18n": "npm run check-i18n --workspace=packages/cli", - "preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run build && npm run typecheck && npm run test:ci && npm run check:serve-fast-path-bundle", + "preflight": "npm run clean && npm ci && npm run format && npm run lint:ci && npm run check:no-webui && npm run build && npm run typecheck && npm run test:ci && npm run check:serve-fast-path-bundle", "postinstall": "patch-package", "prepare": "node scripts/prepare.js", "prepare:package": "node scripts/prepare-package.js", @@ -94,6 +94,7 @@ "changelog": "node scripts/generate-changelog.js", "telemetry": "node scripts/telemetry.js", "check:lockfile": "node scripts/check-lockfile.js", + "check:no-webui": "node scripts/check-no-webui-dependency.js", "check:desktop-isolation": "node scripts/check-desktop-isolation.js", "check:tui-dep-direction": "node scripts/check-tui-dep-direction.mjs", "check:serve-fast-path-bundle": "node scripts/clean-package-build-artifacts.js && npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js", @@ -158,6 +159,7 @@ "msw": "^2.10.4", "npm-run-all": "^4.1.5", "patch-package": "^8.0.1", + "playwright": "^1.57.0", "prettier": "^3.5.3", "react-devtools-core": "^6.1.5", "semver": "^7.7.2", diff --git a/packages/channels/base/src/DaemonChannelBridge.ts b/packages/channels/base/src/DaemonChannelBridge.ts index 124b36120ec..ec41dbc0324 100644 --- a/packages/channels/base/src/DaemonChannelBridge.ts +++ b/packages/channels/base/src/DaemonChannelBridge.ts @@ -554,7 +554,7 @@ export class DaemonChannelBridge removeAttachment ) { try { - // Fan the uploads out like the webui's attachment path: names are + // Fan the uploads out like the browser attachment path: names are // index-disambiguated and prompt order comes from the array order, // so nothing serializes the uploads themselves. const uploads = await Promise.allSettled( diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index 5704df32c48..a8a63acbc0c 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -13983,7 +13983,7 @@ class QwenAgent implements Agent { if (!provisionalWorkspace) { startNonInteractiveOpenAILogHousekeeping(config, settings); } - // ACP sessions served to WebUI clients are interactive: MCP tools can + // ACP sessions served to browser clients are interactive: MCP tools can // arrive progressively, but session creation/loading must not wait for a // slow or wedged server discovery. if (!provisionalWorkspace) { diff --git a/packages/cli/src/commands/review/build-test.test.ts b/packages/cli/src/commands/review/build-test.test.ts index 69871d3eaca..b1008e92e4b 100644 --- a/packages/cli/src/commands/review/build-test.test.ts +++ b/packages/cli/src/commands/review/build-test.test.ts @@ -57,7 +57,12 @@ afterEach(() => reviewSettingsIsolation?.dispose()); const PKGS: WorkspacePackage[] = [ { dir: 'packages/core', name: '@x/core', scripts: ['build'], deps: [] }, - { dir: 'packages/webui', name: '@x/webui', scripts: ['build'], deps: [] }, + { + dir: 'packages/shared-ui', + name: '@x/shared-ui', + scripts: ['build'], + deps: [], + }, ]; describe('unresolvedWorkspaceDeps', () => { @@ -70,9 +75,9 @@ describe('unresolvedWorkspaceDeps', () => { it('finds the workspace package a TS2307 names', () => { const out = - "src/a.ts(23,8): error TS2307: Cannot find module '@x/webui' or its " + + "src/a.ts(23,8): error TS2307: Cannot find module '@x/shared-ui' or its " + 'corresponding type declarations.'; - expect(unresolvedWorkspaceDeps(out, PKGS)).toEqual(['@x/webui']); + expect(unresolvedWorkspaceDeps(out, PKGS)).toEqual(['@x/shared-ui']); }); it('resolves a deep import back to its package', () => { @@ -82,8 +87,11 @@ describe('unresolvedWorkspaceDeps', () => { it("reads a bundler's wording too", () => { expect( - unresolvedWorkspaceDeps('✘ [ERROR] Could not resolve "@x/webui"', PKGS), - ).toEqual(['@x/webui']); + unresolvedWorkspaceDeps( + '✘ [ERROR] Could not resolve "@x/shared-ui"', + PKGS, + ), + ).toEqual(['@x/shared-ui']); }); it('ignores a third-party module — widening cannot fix it, and would loop', () => { @@ -2290,7 +2298,10 @@ describe('runBuildTest', () => { join(root, 'package.json'), JSON.stringify({ name: 'r', workspaces: ['packages/*'] }), ); - pkg('packages/webui', { name: '@x/webui', scripts: { build: 'x' } }); + pkg('packages/shared-ui', { + name: '@x/shared-ui', + scripts: { build: 'x' }, + }); pkg('packages/leaf', { name: '@x/leaf', scripts: { build: 'x', test: 'x' }, @@ -2312,7 +2323,7 @@ describe('runBuildTest', () => { exitCode: null, seconds: 60, timedOut: true, - output: "error TS2307: Cannot find module '@x/webui'", + output: "error TS2307: Cannot find module '@x/shared-ui'", }; } return { diff --git a/packages/cli/src/commands/review/lib/diff-plan.test.ts b/packages/cli/src/commands/review/lib/diff-plan.test.ts index 2ed0e27a9ea..ba55cef8f99 100644 --- a/packages/cli/src/commands/review/lib/diff-plan.test.ts +++ b/packages/cli/src/commands/review/lib/diff-plan.test.ts @@ -43,7 +43,7 @@ describe('classifyPath', () => { const paths = [ 'packages/channels/qqbot/src/events.test.ts', 'src/foo.spec.tsx', - 'packages/webui/src/__tests__/App.tsx', + 'packages/browser-ui/src/__tests__/App.tsx', 'integration-tests/foo.ts', 'pkg/server/handler_test.go', 'app/tests/test_views.py', diff --git a/packages/cli/src/commands/review/lib/npm-toolchain.ts b/packages/cli/src/commands/review/lib/npm-toolchain.ts index 83cecd4cb81..3150bfe815f 100644 --- a/packages/cli/src/commands/review/lib/npm-toolchain.ts +++ b/packages/cli/src/commands/review/lib/npm-toolchain.ts @@ -103,7 +103,7 @@ export function unresolvedWorkspaceDeps( ): string[] { const known = new Map(packages.map((p) => [p.name, p.dir])); const found = new Set(); - // `error TS2307: Cannot find module '@qwen-code/webui' or its corresponding + // `error TS2307: Cannot find module '@scope/workspace' or its corresponding // type declarations.` — and the same shape from a bundler. const re = /Cannot find module '([^']+)'|Could not resolve "([^"]+)"/g; let m: RegExpExecArray | null; diff --git a/packages/cli/src/commands/serve.ts b/packages/cli/src/commands/serve.ts index abe56e01aac..fc20e59a374 100644 --- a/packages/cli/src/commands/serve.ts +++ b/packages/cli/src/commands/serve.ts @@ -559,7 +559,7 @@ export const serveCommand: CommandModule = { .option('allow-origin', { type: 'string', array: true, - description: 'Cross-origin allowlist for browser webui clients.', + description: 'Cross-origin allowlist for browser clients.', }) .option('allow-private-auth-base-url', { type: 'boolean', diff --git a/packages/cli/src/serve/capabilities.ts b/packages/cli/src/serve/capabilities.ts index 4f645fcb932..65826872f15 100644 --- a/packages/cli/src/serve/capabilities.ts +++ b/packages/cli/src/serve/capabilities.ts @@ -313,7 +313,7 @@ export const SERVE_CAPABILITY_REGISTRY = { // can pre-flight whether the daemon will honor their cross-origin // request before issuing it (and parsing a 403). The configured // pattern list is intentionally NOT echoed in the capabilities - // envelope — browser webui knows its own origin, and surfacing the + // envelope — a browser client knows its own origin, and surfacing the // list would let an unauthenticated `/capabilities` reader // enumerate every trusted origin, which is useful recon for a // misconfigured deployment. diff --git a/packages/cli/src/serve/server/session-export.ts b/packages/cli/src/serve/server/session-export.ts index 3e8635ae8d6..814274d51ca 100644 --- a/packages/cli/src/serve/server/session-export.ts +++ b/packages/cli/src/serve/server/session-export.ts @@ -28,7 +28,13 @@ export type SessionExportFormat = (typeof SESSION_EXPORT_FORMATS)[number]; interface ExportFormatDefinition { mimeType: string; - render: (data: ExportSessionData, records?: readonly unknown[]) => string; + // `records` is required because the HTML path cannot render without it: the + // document projector is the only HTML implementation left, and it projects + // from original records rather than from the normalized session data. The + // other formatters take `ExportSessionData` alone and simply ignore the + // second argument, which structural typing allows. Keep the signature shared + // so a caller cannot pick a format and then forget to pass records. + render: (data: ExportSessionData, records: readonly unknown[]) => string; } const EXPORT_FORMATS: Record = { @@ -112,7 +118,7 @@ export async function exportSessionTranscript(params: { mimeType: formatDefinition.mimeType, content: formatDefinition.render( normalized, - format === 'html' ? sessionData.conversation.messages : undefined, + sessionData.conversation.messages, ), }; } diff --git a/packages/cli/src/serve/types.ts b/packages/cli/src/serve/types.ts index 3e1fcf248e6..844d71d25b9 100644 --- a/packages/cli/src/serve/types.ts +++ b/packages/cli/src/serve/types.ts @@ -301,7 +301,7 @@ export interface ServeOptions { timeoutMs?: number; }; /** - * Cross-origin allowlist for browser webui + * Cross-origin allowlist for browser clients * deployments. */ allowOrigins?: string[]; diff --git a/packages/cli/src/ui/commands/exportCommand.ts b/packages/cli/src/ui/commands/exportCommand.ts index 207804b5102..d7f878f296c 100644 --- a/packages/cli/src/ui/commands/exportCommand.ts +++ b/packages/cli/src/ui/commands/exportCommand.ts @@ -32,9 +32,12 @@ import { t } from '../../i18n/index.js'; type ExportFormat = { extension: string; displayName: string; + // Required for the same reason as `ExportFormatDefinition.render` in + // serve/server/session-export.ts: HTML projects from the original records, + // and the remaining formatters ignore this argument. format: ( sessionData: ExportSessionData, - records?: readonly unknown[], + records: readonly unknown[], ) => string; }; @@ -320,10 +323,7 @@ async function exportSessionAction( config, ); - const content = exportFormat.format( - normalizedData, - exportFormat.extension === 'html' ? conversation.messages : undefined, - ); + const content = exportFormat.format(normalizedData, conversation.messages); if (target.outputDirKind === 'custom') { try { diff --git a/packages/cli/src/ui/utils/export/formatters/html.test.ts b/packages/cli/src/ui/utils/export/formatters/html.test.ts index eec1f745bf6..d579ff6f7a4 100644 --- a/packages/cli/src/ui/utils/export/formatters/html.test.ts +++ b/packages/cli/src/ui/utils/export/formatters/html.test.ts @@ -48,31 +48,31 @@ const records = [ ]; describe('HTML export formatter', () => { - it('preserves the legacy formatter when source records are unavailable', () => { - const html = toHtml(sessionData); - - expect(html).toContain('id="chat-data"'); - expect(html).not.toContain('id="transcript-document"'); - }); - it('uses the version-bound document renderer for the product export path', () => { const html = toHtml(sessionData, records); const secondHtml = toHtml(sessionData, records); const nonce = html.match(/script-src 'nonce-([^']+)'/)?.[1]; const secondNonce = secondHtml.match(/script-src 'nonce-([^']+)'/)?.[1]; - expect(html).toContain('id="transcript-document"'); expect(html).toContain('Hello from the document exporter.'); expect(html).toContain("connect-src 'none'"); + expect(html).toMatch(/script-src 'nonce-[^']+';/); + expect(html).toContain( + `https://unpkg.com/@qwen-code/qwen-code@${EXPORT_TRANSCRIPT_RENDERER_VERSION.split('+')[0]}/export-transcript-document.js`, + ); + expect(EXPORT_TRANSCRIPT_RENDERER_VERSION).toMatch( + /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?\+[a-f0-9]{16}$/, + ); + expect(html).toMatch(/integrity="sha384-[A-Za-z0-9+/]{64}"/); + expect(html).toContain('crossorigin="anonymous"'); + expect(html).not.toContain('qwen-code-assets.oss-cn-hangzhou.aliyuncs.com'); + expect(html).not.toContain('type="importmap"'); + expect(html).not.toContain('type="module"'); + expect(html.length).toBeLessThan(500_000); expect(html).not.toContain('id="chat-data"'); expect(html).not.toContain('session-secret'); expect(html).not.toContain('/home/alice'); expect(html).not.toContain('__EXPORT_NONCE__'); - expect(html).toContain('data-document-metadata'); - expect(html).toContain('Context Usage'); - expect(html).toContain('data-document-expand-all'); - expect(html).toContain('data-document-collapse-all'); - expect(html).toContain('data-document-theme-toggle'); expect(nonce).toBeTruthy(); expect(secondNonce).toBeTruthy(); expect(secondNonce).not.toBe(nonce); diff --git a/packages/cli/src/ui/utils/export/formatters/html.ts b/packages/cli/src/ui/utils/export/formatters/html.ts index c03aa75665d..652abbc361c 100644 --- a/packages/cli/src/ui/utils/export/formatters/html.ts +++ b/packages/cli/src/ui/utils/export/formatters/html.ts @@ -7,7 +7,6 @@ import type { ExportSessionData } from '../types.js'; import { randomBytes } from 'node:crypto'; import { - EXPORT_HTML_TEMPLATE as HTML_TEMPLATE, EXPORT_TRANSCRIPT_HTML_TEMPLATE, EXPORT_TRANSCRIPT_RENDERER_LIMITS, EXPORT_TRANSCRIPT_RENDERER_VERSION, @@ -20,61 +19,6 @@ import { } from '../export-transcript-document.js'; import { escapeJsonForHtmlScriptData } from '../html-script-data.js'; -/** - * Loads the HTML template built from assets. - */ -export function loadHtmlTemplate(): string { - return HTML_TEMPLATE; -} - -/** - * Injects JSON data into the HTML template. - */ -export function injectDataIntoHtmlTemplate( - template: string, - data: { - sessionId: string; - startTime: string; - messages: unknown[]; - metadata?: unknown; - }, -): string { - const jsonData = JSON.stringify(data, null, 2); - const escapedJsonData = escapeJsonForHtmlScriptData(jsonData); - const idAttribute = 'id="chat-data"'; - const idIndex = template.indexOf(idAttribute); - if (idIndex === -1) { - return template; - } - - const openTagStart = template.lastIndexOf('', idIndex); - if (openTagEnd === -1) { - return template; - } - - const closeTagStart = template.indexOf('', openTagEnd); - if (closeTagStart === -1) { - return template; - } - - const lineStart = template.lastIndexOf('\n', openTagStart); - const lineIndent = - lineStart === -1 ? '' : template.slice(lineStart + 1, openTagStart); - const indentedJson = escapedJsonData - .split('\n') - .map((line) => `${lineIndent}${line}`) - .join('\n'); - - const before = template.slice(0, openTagEnd + 1); - const after = template.slice(closeTagStart); - return `${before}\n${indentedJson}\n${after}`; -} - export function injectDocumentIntoHtmlTemplate( template: string, document: unknown, @@ -137,20 +81,15 @@ function injectJsonScript( */ export function toHtml( sessionData: ExportSessionData, - originalRecords?: readonly unknown[], + originalRecords: readonly unknown[], ): string { - if (originalRecords) { - const document = createExportTranscriptDocumentV1( - originalRecords, - sessionData, - { - rendererVersion: EXPORT_TRANSCRIPT_RENDERER_VERSION, - exportedAt: - sessionData.metadata?.exportTime ?? new Date().toISOString(), - }, - ); - return renderExportTranscriptDocumentToHtml(document); - } - const template = loadHtmlTemplate(); - return injectDataIntoHtmlTemplate(template, sessionData); + const document = createExportTranscriptDocumentV1( + originalRecords, + sessionData, + { + rendererVersion: EXPORT_TRANSCRIPT_RENDERER_VERSION, + exportedAt: sessionData.metadata?.exportTime ?? new Date().toISOString(), + }, + ); + return renderExportTranscriptDocumentToHtml(document); } diff --git a/packages/cli/src/ui/utils/export/index.ts b/packages/cli/src/ui/utils/export/index.ts index 609c7583d62..b4e8e91df9d 100644 --- a/packages/cli/src/ui/utils/export/index.ts +++ b/packages/cli/src/ui/utils/export/index.ts @@ -14,8 +14,6 @@ export { normalizeSessionData } from './normalize.js'; export { toMarkdown } from './formatters/markdown.js'; export { toHtml, - loadHtmlTemplate, - injectDataIntoHtmlTemplate, injectDocumentIntoHtmlTemplate, renderExportTranscriptDocumentToHtml, } from './formatters/html.js'; diff --git a/packages/core/src/core/permission-helpers.ts b/packages/core/src/core/permission-helpers.ts index b65e786cf0a..86146f32c0c 100644 --- a/packages/core/src/core/permission-helpers.ts +++ b/packages/core/src/core/permission-helpers.ts @@ -8,7 +8,7 @@ * Shared permission-evaluation and persistence helpers. * * These are used by both `coreToolScheduler` (CLI mode) and the ACP - * `Session` (VS Code / webui mode) so that the L3→L4→L5 permission flow + * `Session` (VS Code / browser mode) so that the L3→L4→L5 permission flow * and the "Always Allow" persistence logic stay in sync. */ diff --git a/packages/core/src/followup/followupState.ts b/packages/core/src/followup/followupState.ts index e62c5e03134..eda3c606e47 100644 --- a/packages/core/src/followup/followupState.ts +++ b/packages/core/src/followup/followupState.ts @@ -6,7 +6,8 @@ * Shared Follow-up Suggestions State Logic * * Framework-agnostic state management for prompt suggestions, - * shared between CLI (Ink) and WebUI (React) hooks. + * used by the CLI (Ink) follow-up hook; Web Shell's daemon followup hook + * maintains its own copy. */ /** @@ -102,8 +103,8 @@ export interface FollowupControllerActions { * Creates a framework-agnostic followup suggestion controller. * * Encapsulates timer management, accept debounce, and state transitions so - * that React hooks (CLI and WebUI) only need thin wrappers around - * `useState` + this controller. + * that React hooks only need thin wrappers around `useState` + this + * controller. */ export function createFollowupController( options: FollowupControllerOptions, diff --git a/packages/core/src/services/sessionService.ts b/packages/core/src/services/sessionService.ts index 30bee0face3..23115daa3c3 100644 --- a/packages/core/src/services/sessionService.ts +++ b/packages/core/src/services/sessionService.ts @@ -407,7 +407,7 @@ const SESSION_LIST_CANCEL_YIELD_INTERVAL = 128; /** * Maximum character length for a session custom title. - * Shared across CLI, WebUI, VSCode, and ACP. + * Shared across CLI, browser, VS Code, and ACP clients. */ export const SESSION_TITLE_MAX_LENGTH = 200; @@ -3718,7 +3718,7 @@ export class SessionService { // Append a custom_title system record. `renameSession` is the // fallback path when no live recording service is attached (e.g., from - // the WebUI or VSCode extension). Callers pass `titleSource='auto'` + // Web Shell or the VS Code extension). Callers pass `titleSource='auto'` // only when the title came from the auto-generator; defaults to // 'manual' for explicit user renames. const titleRecord: ChatRecord = { diff --git a/packages/sdk-typescript/src/daemon/DaemonClient.ts b/packages/sdk-typescript/src/daemon/DaemonClient.ts index cceb9c69785..c8841317940 100644 --- a/packages/sdk-typescript/src/daemon/DaemonClient.ts +++ b/packages/sdk-typescript/src/daemon/DaemonClient.ts @@ -497,7 +497,7 @@ function stripTrailingSlashes(url: string): string { * * Defensive on three axes: * 1. **Browser-safe**: `globalThis.process` indirection. The SDK is - * imported by `@qwen-code/webui`; a literal + * imported by `@qwen-code/web-shell`; a literal * `process.env[...]` would explode at module load on browser * bundles. Browser globals don't expose `process` so this returns * `undefined` cleanly there. diff --git a/packages/sdk-typescript/src/daemon/events.ts b/packages/sdk-typescript/src/daemon/events.ts index 304a32b7354..0b754c307ca 100644 --- a/packages/sdk-typescript/src/daemon/events.ts +++ b/packages/sdk-typescript/src/daemon/events.ts @@ -142,7 +142,7 @@ export const DAEMON_KNOWN_EVENT_TYPE_VALUES = [ // Daemon assist push events. `followup_suggestion`: server-side // ghost-text "what you might want to ask next" suggestion, generated // after each end_turn by the ACP child and forwarded through the per- - // session SSE bus so the webui (and other future daemon adapters) + // session SSE bus so Web Shell (and other future daemon adapters) // can render the suggestion in their input placeholder. The wire // carries only post-filter suggestions (`getFilterReason()===null`); // generator-side suppression telemetry stays on the daemon. Old SDK diff --git a/packages/sdk-typescript/src/daemon/ui/transcript.ts b/packages/sdk-typescript/src/daemon/ui/transcript.ts index e5da8fd542c..45ff728efe1 100644 --- a/packages/sdk-typescript/src/daemon/ui/transcript.ts +++ b/packages/sdk-typescript/src/daemon/ui/transcript.ts @@ -1238,7 +1238,7 @@ function discardToolBlock( * The task-display projection carries exactly these two `executionMode` * literals. Fail closed: any other value (corrupted recording, future runtime * mode) must fall back to the legacy argument/status heuristic instead of - * forcing a classification. Both consumer-side whitelists — webui's + * forcing a classification. Both consumer-side whitelists — Web Shell's * `projectSubagentToolUpdate` and web-shell's `daemonToolBlockToToolCall` — * call this single guard so live-summary and recorded-transcript clients * accept the same literal set; when a third mode lands, extend it here once. diff --git a/packages/sdk-typescript/src/daemon/ui/types.ts b/packages/sdk-typescript/src/daemon/ui/types.ts index 22cde2cc5ea..ade558dc24f 100644 --- a/packages/sdk-typescript/src/daemon/ui/types.ts +++ b/packages/sdk-typescript/src/daemon/ui/types.ts @@ -342,7 +342,7 @@ export type DaemonUnrecognizedDiagnosticReason = /** * Membership over the runtime reason array, exported so every routing guard - * (reducer sidechannel here, provider flush/drop guard pair in webui) + * (reducer sidechannel here, provider flush/drop guard pair in Web Shell) * classifies against one source. A reason added to the array routes onto the * sidechannel everywhere without hand-editing each consumer (#8823 review). */ diff --git a/packages/sdk-typescript/test/unit/daemonUi.test.ts b/packages/sdk-typescript/test/unit/daemonUi.test.ts index 12949844a98..17b2d5d0954 100644 --- a/packages/sdk-typescript/test/unit/daemonUi.test.ts +++ b/packages/sdk-typescript/test/unit/daemonUi.test.ts @@ -7637,11 +7637,9 @@ describe('Late permission.resolved after sentinel pruned (wenshao R3 qwen3.7-max }); }); -// Note: webui transcriptAdapter previewMarkdown/rawOutput preservation -// test lives in packages/webui/src/daemon/transcriptAdapter.test.ts — -// keeping it co-located with the adapter ensures path resolution goes -// through webui's tsconfig path-mapping into source rather than the -// SDK dist (which doesn't exist in CI before this PR builds). +// Note: the previewMarkdown/rawOutput enrichment path retired with the +// webui package; Web Shell's transcriptAdapter has no such enrichment, +// so no co-located preservation test exists for it in this repo. describe('ensureSafeImageUrl tightened to data:image/* (audit follow-up)', () => { it('allows http/https/data:image/* but rejects data:text/html', async () => { diff --git a/packages/web-shell/client/constants/sessions.ts b/packages/web-shell/client/constants/sessions.ts index 92b2f802120..fa3f266c59d 100644 --- a/packages/web-shell/client/constants/sessions.ts +++ b/packages/web-shell/client/constants/sessions.ts @@ -29,7 +29,7 @@ export const WEB_SHELL_TRANSCRIPT_RELOAD_BLOCKS = 500; * * Intentionally equal to the provider's `DEFAULT_MAX_BLOCKS`; the equality is * enforced by `sessions.test.ts` (importing the constant here instead of the - * provider would pull the webui barrel into every importer's module graph and + * provider would pull the browser UI barrel into every importer's module graph and * break the enumerative `daemon-react-sdk` mocks in component tests). Bounding * the window keeps very long sessions responsive: the per-dispatch reducer * cost (a full block-array copy) and the full-list message normalization turn diff --git a/packages/web-shell/client/daemon/session/clientLifecycle.ts b/packages/web-shell/client/daemon/session/clientLifecycle.ts index b51b30e6579..30db266a01c 100644 --- a/packages/web-shell/client/daemon/session/clientLifecycle.ts +++ b/packages/web-shell/client/daemon/session/clientLifecycle.ts @@ -4,14 +4,15 @@ * SPDX-License-Identifier: Apache-2.0 */ -const WEBUI_SESSION_CLIENT_ID_PREFIX = 'qwen-code-webui-client-id:session:'; +// Keep the persisted key stable across the WebUI-to-Web-Shell migration. +const SESSION_CLIENT_ID_STORAGE_PREFIX = 'qwen-code-webui-client-id:session:'; export function getStableClientId( clientId: string | undefined, sessionId?: string, ): string { if (clientId) return clientId; - if (typeof window === 'undefined') return createWebuiClientId(); + if (typeof window === 'undefined') return createWebShellClientId(); try { if (sessionId) { const existingSessionClientId = window.sessionStorage.getItem( @@ -19,9 +20,9 @@ export function getStableClientId( ); if (existingSessionClientId) return existingSessionClientId; } - return createWebuiClientId(); + return createWebShellClientId(); } catch { - return createWebuiClientId(); + return createWebShellClientId(); } } @@ -75,7 +76,7 @@ export async function detachDaemonClient(opts: { throw new Error(`Detach client failed (${res.status})`); } -function createWebuiClientId(): string { +function createWebShellClientId(): string { const random = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function' ? crypto.randomUUID() @@ -84,7 +85,7 @@ function createWebuiClientId(): string { } function sessionClientIdKey(sessionId: string): string { - return `${WEBUI_SESSION_CLIENT_ID_PREFIX}${encodeURIComponent(sessionId)}`; + return `${SESSION_CLIENT_ID_STORAGE_PREFIX}${encodeURIComponent(sessionId)}`; } function stripTrailingSlashes(url: string): string { diff --git a/packages/web-shell/client/daemon/useDaemonFollowupSuggestion.ts b/packages/web-shell/client/daemon/useDaemonFollowupSuggestion.ts index d07eee91667..872cd6867b5 100644 --- a/packages/web-shell/client/daemon/useDaemonFollowupSuggestion.ts +++ b/packages/web-shell/client/daemon/useDaemonFollowupSuggestion.ts @@ -224,7 +224,7 @@ export interface UseDaemonFollowupSuggestionReturn { /** * Wire the daemon's server-pushed `followup_suggestion` event into the - * webui's ``. Consumers: + * Web Shell's composer. Consumers: * * 1. Render `` with the three values returned here. diff --git a/packages/web-templates/src/export-html/build.mjs b/packages/web-templates/src/export-html/build.mjs index 07f63207115..47712bad44f 100644 --- a/packages/web-templates/src/export-html/build.mjs +++ b/packages/web-templates/src/export-html/build.mjs @@ -1,105 +1,39 @@ -import { readFile, writeFile, mkdir } from 'node:fs/promises'; -import { createRequire } from 'node:module'; +import { readFile, writeFile, mkdir, rm } from 'node:fs/promises'; import { fileURLToPath } from 'node:url'; import { dirname, join } from 'node:path'; +import { createHash } from 'node:crypto'; import { build } from 'esbuild'; -import { build as viteBuild } from 'vite'; -import { buildConfig } from './esbuild.config.mjs'; -import prettier from 'prettier'; -const require = createRequire(import.meta.url); const assetsDir = dirname(fileURLToPath(import.meta.url)); const srcDir = join(assetsDir, 'src'); const assetsDistDir = join(assetsDir, 'dist'); const generatedDir = join(assetsDir, '..', 'generated'); -const webuiDir = join(assetsDir, '..', '..', '..', 'webui'); await mkdir(generatedDir, { recursive: true }); +await rm(assetsDistDir, { recursive: true, force: true }); await mkdir(assetsDistDir, { recursive: true }); +await rm(join(generatedDir, 'exportHtmlTemplate.ts'), { force: true }); -const templateModulePath = join(generatedDir, 'exportHtmlTemplate.ts'); const documentTemplateModulePath = join( generatedDir, 'exportTranscriptDocumentTemplate.ts', ); -const reactUmdVersion = '18.2.0'; -const reactDomUmdVersion = '18.2.0'; const exportTranscriptMaxBlocks = 1_000; const exportTranscriptMaxEnvelopeBytes = 32 * 1024 * 1024; -const { version: exportTranscriptRendererVersion } = JSON.parse( - await readFile(join(assetsDir, '..', '..', 'package.json'), 'utf8'), +const { version: exportTranscriptRendererPackageVersion } = JSON.parse( + await readFile( + join(assetsDir, '..', '..', '..', '..', 'package.json'), + 'utf8', + ), ); +const documentRendererUrl = `https://unpkg.com/@qwen-code/qwen-code@${exportTranscriptRendererPackageVersion}/export-transcript-document.js`; +const rendererVersionPlaceholder = '__QWEN_RENDERER_BUILD_ID__'; -// Build the @qwen-code/webui viewer as a UMD bundle from webui sources and -// inline it into the template below. The exported HTML must stay -// self-contained: @qwen-code/webui is not published as part of the release -// flow, and resolving it from a CDN at view time would retroactively change -// how every already-generated export renders. Building it here (instead of -// reading packages/webui/dist) keeps the template build independent of -// webui's own output and prevents a stale dist directory from changing the -// generated export. -const webuiDistDir = join(assetsDistDir, 'webui'); -await viteBuild({ - configFile: false, - root: webuiDir, - logLevel: 'warn', - css: { - postcss: { - plugins: [ - require('tailwindcss')({ - presets: [require(join(webuiDir, 'tailwind.preset.cjs'))], - content: [join(webuiDir, 'src', '**', '*.{ts,tsx}')], - }), - require('autoprefixer'), - ], - }, - }, - build: { - outDir: webuiDistDir, - emptyOutDir: true, - minify: true, - sourcemap: false, - cssCodeSplit: false, - lib: { - entry: join(webuiDir, 'src', 'index.ts'), - name: 'QwenCodeWebUI', - formats: ['umd'], - fileName: () => 'index.umd.js', - }, - rollupOptions: { - external: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'], - output: { - globals: { - react: 'React', - 'react-dom': 'ReactDOM', - 'react-dom/client': 'ReactDOM', - 'react/jsx-runtime': 'ReactJSXRuntime', - }, - assetFileNames: 'styles.[ext]', - }, - }, - }, -}); -const webuiJs = (await readFile(join(webuiDistDir, 'index.umd.js'), 'utf8')) - // Escape for inlining into a - - - - - - - - - - - - - - - -
- - - - -``` - -### Option 2: Without JSX (using React.createElement directly) - -```html - - - - - - - - - - - - - - - - - -
- - - - -``` - -For a complete working example, see [examples/cdn-usage-demo.html](./examples/cdn-usage-demo.html). - -## Quick Start - -```tsx -import { Button, Input, Tooltip } from '@qwen-code/webui'; -import { PlatformProvider } from '@qwen-code/webui/context'; - -function App() { - return ( - - - - ); -} -``` - -### Dual-mode usage (chat + terminal share one session) - -Wrap both views with a **single** ``. Both panels share one SSE connection and one transcript store. - -```tsx - - - - - - -``` - -Do NOT nest multiple `` for the same session — that creates two SSE connections and potential state divergence. - -### Session hooks - -| Hook | Returns | -| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `useTranscriptBlocks()` | `readonly DaemonTranscriptBlock[]` (raw blocks) | -| `useTranscriptState()` | Full `DaemonTranscriptState` (blocks + metadata) | -| `useActions()` | `{ sendPrompt, cancel, setModel, setApprovalMode, respondToPermission, loadSession, newSession, ... }` | -| `useConnection()` | `{ status, sessionId, clientId, workspaceCwd, currentModel, currentMode, capabilities, commands, skills, models, tokenCount, tokenUsage, contextWindow, loadingTranscript, missingSession, … }` | -| `useDaemonSessionOwnerGuard()` | Captures the current attachment identity so stale async UI continuations can be ignored | -| `useStreamingState()` | `'idle' \| 'waiting' \| 'responding' \| 'thinking'` | -| `usePromptStatus()` | `'idle' \| 'waiting' \| 'streaming'` | -| `usePendingPermissions()` | Unresolved permission blocks | -| `useActiveTodoList()` | Latest todo list, only when it still has active items | - -### Workspace hooks - -Require an ancestor ``: - -| Hook | Description | -| ----------------------- | ------------------------------------------------------------------------ | -| `useMcp(options?)` | MCP server list + restart + tools | -| `useSkills(options?)` | Available skills (read-only) | -| `useTools(options?)` | Workspace tools + enable/disable | -| `useMemory(options?)` | Memory files + read/write | -| `useAgents(options?)` | Agent CRUD | -| `useSessions(options?)` | Session list (switch/new/release require nested `DaemonSessionProvider`) | -| `useFiles()` | File operations: glob, read, write, edit, stat | -| `useGlob()` | `globWorkspace(pattern, opts)` | -| `useWorkspace()` | Full workspace context value | -| `useWorkspaceActions()` | All workspace-level actions | - -All resource hooks accept `{ autoLoad?: boolean, enabled?: boolean }` and return `{ data, loading, error, reload }`. When nested under an active `DaemonSessionProvider`, resource hooks also refresh from daemon workspace events that are already broadcast on the session stream (`memory_changed`, `agent_changed`, `tool_toggled`, MCP restart events, and workspace init events). Without an active session, hooks remain pull-based. - -### Props - -**`DaemonSessionProviderProps`:** - -| Prop | Type | Default | Description | -| --------------------- | --------- | --------- | -------------------------------------------------------------------------------------------------------- | -| `baseUrl` | `string?` | inherited | Daemon HTTP base URL (inherited from `DaemonWorkspaceProvider` when nested; required in standalone mode) | -| `token` | `string?` | inherited | Bearer token (inherited from `DaemonWorkspaceProvider` when nested) | -| `workspaceCwd` | `string?` | — | Override workspace path (uses capabilities if omitted) | -| `sessionId` | `string?` | — | Restore a specific session and control later session switches | -| `clientId` | `string?` | — | Override stable client ID (auto-generated if omitted) | -| `autoConnect` | `boolean` | `true` | Connect on mount | -| `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect | -| `reconnectDelayMs` | `number` | `1000` | Initial reconnect backoff | -| `maxReconnectDelayMs` | `number` | `10000` | Max reconnect backoff | -| `suppressOwnUserEcho` | `boolean` | `true` | Suppress own user message echoes | - -**`DaemonWorkspaceProviderProps`:** - -| Prop | Type | Default | Description | -| -------------- | --------- | -------- | --------------------------------------- | -| `baseUrl` | `string` | required | Daemon HTTP base URL | -| `token` | `string?` | — | Bearer token | -| `workspaceCwd` | `string?` | — | Override workspace path | -| `autoConnect` | `boolean` | `true` | Connect and fetch capabilities on mount | - -## Components - -### UI Components - -#### Button - -```tsx -import { Button } from '@qwen-code/webui'; - -; -``` - -**Props:** - -- `variant`: 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline' -- `size`: 'sm' | 'md' | 'lg' -- `loading`: boolean -- `leftIcon`: ReactNode -- `rightIcon`: ReactNode -- `fullWidth`: boolean - -#### Input - -```tsx -import { Input } from '@qwen-code/webui'; - -; -``` - -**Props:** - -- `size`: 'sm' | 'md' | 'lg' -- `error`: boolean -- `errorMessage`: string -- `label`: string -- `helperText`: string -- `leftElement`: ReactNode -- `rightElement`: ReactNode - -#### Tooltip - -```tsx -import { Tooltip } from '@qwen-code/webui'; - - - Hover me -; -``` - -### Icons - -```tsx -import { FileIcon, FolderIcon, CheckIcon } from '@qwen-code/webui/icons'; - -; -``` - -Available icon categories: - -- **FileIcons**: FileIcon, FolderIcon, SaveDocumentIcon -- **StatusIcons**: CheckIcon, ErrorIcon, WarningIcon, LoadingIcon -- **NavigationIcons**: ArrowLeftIcon, ArrowRightIcon, ChevronIcon -- **EditIcons**: EditIcon, DeleteIcon, CopyIcon -- **SpecialIcons**: SendIcon, StopIcon, CloseIcon - -### Layout Components - -- `Container`: Main layout wrapper -- `Header`: Application header -- `Footer`: Application footer -- `Sidebar`: Side navigation -- `Main`: Main content area - -### Message Components - -- `Message`: Chat message display -- `MessageList`: List of messages -- `MessageInput`: Message input field -- `WaitingMessage`: Loading/waiting state -- `InterruptedMessage`: Interrupted state display - -## Platform Context - -The Platform Context provides an abstraction layer for platform-specific capabilities: - -```tsx -import { PlatformProvider, usePlatform } from '@qwen-code/webui/context'; - -const platformContext = { - postMessage: (message) => vscode.postMessage(message), - onMessage: (handler) => { - window.addEventListener('message', handler); - return () => window.removeEventListener('message', handler); - }, - openFile: (path) => { - /* platform-specific */ - }, - platform: 'vscode', -}; - -function App() { - return ( - - - - ); -} - -function Component() { - const { postMessage, platform } = usePlatform(); - // Use platform capabilities -} -``` - -## Tailwind Preset - -Use the shared Tailwind preset for consistent styling: - -```js -// tailwind.config.js -module.exports = { - presets: [require('@qwen-code/webui/tailwind.preset.cjs')], - // your customizations -}; -``` - -## Development - -### Running Storybook - -```bash -cd packages/webui -npm run storybook -``` - -### Building - -```bash -npm run build -``` - -### Type Checking - -```bash -npm run typecheck -``` - -## Project Structure - -``` -packages/webui/ -├── src/ -│ ├── components/ -│ │ ├── icons/ # Icon components -│ │ ├── layout/ # Layout components -│ │ ├── messages/ # Message components -│ │ └── ui/ # UI primitives -│ ├── context/ # Platform context -│ ├── hooks/ # Custom hooks -│ └── types/ # Type definitions -├── .storybook/ # Storybook config -├── tailwind.preset.cjs # Shared Tailwind preset -└── vite.config.ts # Build configuration -``` - -## License - -Apache-2.0 diff --git a/packages/webui/docs/WEBUI_MIGRATION_PLAN_EN.md b/packages/webui/docs/WEBUI_MIGRATION_PLAN_EN.md deleted file mode 100644 index 9e60de1e437..00000000000 --- a/packages/webui/docs/WEBUI_MIGRATION_PLAN_EN.md +++ /dev/null @@ -1,428 +0,0 @@ -# WebUI Component Library Extraction Plan - -## 1. Background and Goals - -### 1.1 Background - -`packages/vscode-ide-companion` is a VSCode extension whose core content is a WebView page with UI components provided by React. As the product line expands, more scenarios require building products with Web UI: - -- **Chrome Browser Extension** - Sidebar chat interface -- **Web Chat Page** - Pure web application -- **Conversation Share Page** - Render conversations as static HTML - -For excellent software engineering architecture, we need to unify and reuse UI components across products. - -### 1.2 Goals - -1. Extract components from `vscode-ide-companion/src/webview/` into an independent `@qwen-code/webui` package -2. Establish a layered architecture: Pure UI components + Business UI components -3. Use Vite + Storybook for development and component showcase -4. Abstract platform capabilities through Platform Context for cross-platform reuse -5. Provide Tailwind CSS preset to ensure UI consistency across products - ---- - -## 2. Current State Analysis - -### 2.1 Current Code Structure - -`packages/vscode-ide-companion/src/webview/` contains 77 files: - -``` -webview/ -├── App.tsx # Main entry -├── components/ -│ ├── icons/ # 8 icon components -│ ├── layout/ # 8 layout components -│ │ ├── ChatHeader.tsx -│ │ ├── InputForm.tsx -│ │ ├── SessionSelector.tsx -│ │ ├── EmptyState.tsx -│ │ ├── Onboarding.tsx -│ │ └── ... -│ ├── messages/ # Message display components -│ │ ├── UserMessage.tsx -│ │ ├── Assistant/ -│ │ ├── MarkdownRenderer/ -│ │ ├── ThinkingMessage.tsx -│ │ ├── Waiting/ -│ │ └── toolcalls/ # 16 tool call components -│ ├── PermissionDrawer/ # Permission request drawer -│ └── Tooltip.tsx -├── hooks/ # Custom hooks -├── handlers/ # Message handlers -├── styles/ # CSS styles -└── utils/ # Utility functions -``` - -### 2.2 Key Dependency Analysis - -**Platform Coupling Points:** - -- `useVSCode` hook - Calls `acquireVsCodeApi()` for message communication -- `handlers/` - Handles VSCode message protocol -- Some type definitions come from `../types/` directory - -``` -┌─────────────────────────────────────────────────────────┐ -│ App.tsx (Entry) │ -├─────────────────────────────────────────────────────────┤ -│ hooks/ │ handlers/ │ components/ │ -│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤ -│ ├─useSession │ ├─MessageRouter │ ├─icons/ │ -│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │ -│ └─... │ └─... │ ├─messages/ │ -│ │ │ └─PermDrawer/ │ -├─────────────────────────────────────────────────────────┤ -│ VSCode API (acquireVsCodeApi) │ -└─────────────────────────────────────────────────────────┘ -``` - ---- - -## 3. Target Architecture - -### 3.1 Layered Architecture Design - -``` -┌─────────────────────────────────────────────────────────┐ -│ Layer 3: Platform Adapters │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │ -│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ -├─────────┼────────────────┼────────────────┼────────────┤ -│ │ │ │ │ -│ ▼ ▼ ▼ │ -│ ┌─────────────────────────────────────────────────┐ │ -│ │ Platform Context Provider │ │ -│ └─────────────────────────────────────────────────┘ │ -├─────────────────────────────────────────────────────────┤ -│ Layer 2: Chat Components │ -│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ -│ │ MessageList│ │ ChatHeader │ │ InputForm │ │ -│ └────────────┘ └────────────┘ └────────────┘ │ -├─────────────────────────────────────────────────────────┤ -│ Layer 1: Primitives (Pure UI) │ -│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ -│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │ -│ └────────┘ └────────┘ └────────┘ └────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -### 3.2 Platform Context Design - -```typescript -// @qwen-code/webui/src/context/PlatformContext.ts -interface PlatformContext { - // Message communication - postMessage: (message: unknown) => void; - onMessage: (handler: (message: unknown) => void) => () => void; - - // File operations - openFile?: (path: string) => void; - attachFile?: () => void; - - // Authentication - login?: () => void; - - // Platform info - platform: 'vscode' | 'chrome' | 'web' | 'share'; -} -``` - ---- - -## 4. Technical Solution - -### 4.1 Build Configuration (Vite Library Mode) - -**Output formats:** - -- ESM (`dist/index.js`) - Primary format -- CJS (`dist/index.cjs`) - Compatibility -- TypeScript declarations (`dist/index.d.ts`) - -```javascript -// vite.config.ts -export default defineConfig({ - build: { - lib: { - entry: resolve(__dirname, 'src/index.ts'), - formats: ['es', 'cjs'], - fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`, - }, - rollupOptions: { - external: ['react', 'react-dom'], - }, - }, -}); -``` - -### 4.2 Tailwind Preset Solution - -```javascript -// @qwen-code/webui/tailwind.preset.js -module.exports = { - theme: { - extend: { - colors: { - 'app-primary': 'var(--app-primary)', - 'app-background': 'var(--app-primary-background)', - 'app-foreground': 'var(--app-primary-foreground)', - }, - }, - }, -}; - -// Consumer's tailwind.config.js -module.exports = { - presets: [require('@qwen-code/webui/tailwind.preset')], - content: [ - './src/**/*.{ts,tsx}', - './node_modules/@qwen-code/webui/dist/**/*.js', - ], -}; -``` - -### 4.3 Storybook Configuration - -``` -packages/webui/ -├── .storybook/ -│ ├── main.ts # Storybook config -│ ├── preview.ts # Global decorators -│ └── manager.ts # UI config -└── src/ - └── stories/ # Story files -``` - ---- - -## 5. Component Migration Classification - -### 5.1 Batch 1: No-dependency Components (Ready to migrate) - -| Component | Source Path | Complexity | Notes | -| ------------------ | ------------------------ | ---------- | --------------------------- | -| Icons | `components/icons/` | Low | 8 icon components, pure SVG | -| Tooltip | `components/Tooltip.tsx` | Low | Pure UI | -| WaitingMessage | `messages/Waiting/` | Low | Loading state display | -| InterruptedMessage | `messages/Waiting/` | Low | Interrupted state display | - -### 5.2 Batch 2: Light-dependency Components (Need props abstraction) - -| Component | Source Path | Dependency | Refactoring | -| ---------------- | ------------------------------ | ----------- | ---------------- | -| UserMessage | `messages/UserMessage.tsx` | onFileClick | Props injection | -| AssistantMessage | `messages/Assistant/` | onFileClick | Props injection | -| ThinkingMessage | `messages/ThinkingMessage.tsx` | onFileClick | Props injection | -| MarkdownRenderer | `messages/MarkdownRenderer/` | None | Direct migration | -| EmptyState | `layout/EmptyState.tsx` | None | Direct migration | -| ChatHeader | `layout/ChatHeader.tsx` | callbacks | Props injection | - -### 5.3 Batch 3: Medium-dependency Components (Need Context) - -| Component | Source Path | Dependency | Refactoring | -| ------------------- | ---------------------------- | --------------------- | ----------------- | -| InputForm | `layout/InputForm.tsx` | Multiple callbacks | Context + Props | -| SessionSelector | `layout/SessionSelector.tsx` | session data | Props injection | -| CompletionMenu | `layout/CompletionMenu.tsx` | items data | Props injection | -| PermissionDrawer | `PermissionDrawer/` | callbacks | Context + Props | -| ToolCall components | `messages/toolcalls/` | Various tool displays | Modular migration | - -### 5.4 Batch 4: Heavy-dependency (Keep in platform package) - -| Component/Module | Notes | -| ---------------- | ------------------------------------------------- | -| App.tsx | Main entry, contains business orchestration logic | -| hooks/ | Most require platform adaptation | -| handlers/ | VSCode message handling | -| Onboarding | Authentication related, platform-specific | - ---- - -## 6. Incremental Migration Strategy - -### 6.1 Migration Principles - -1. **Bidirectional compatibility**: During migration, vscode-ide-companion can import from both webui and local -2. **One-by-one replacement**: For each migrated component, replace import path in VSCode extension and verify -3. **No breaking changes**: Ensure the extension builds and runs normally after each migration - -### 6.2 Migration Workflow - -``` -Developer ──► @qwen-code/webui ──► vscode-ide-companion - │ │ │ - │ 1. Copy component to webui │ - │ 2. Add Story for verification │ - │ 3. Export from index.ts │ - │ │ │ - │ └──────────────────────┤ - │ │ - │ 4. Update import path - │ 5. Delete original component - │ 6. Build and test -``` - -### 6.3 Example: Migrating Icons - -```typescript -// Before: vscode-ide-companion/src/webview/components/icons/index.ts -export { FileIcon } from './FileIcons.js'; - -// After: Update import -import { FileIcon } from '@qwen-code/webui'; -// or import { FileIcon } from '@qwen-code/webui/icons'; -``` - ---- - -## 7. Task Breakdown - -### Phase 0: Infrastructure Setup (Prerequisites) - -- [ ] **T0-1**: Vite build configuration -- [ ] **T0-2**: Storybook configuration -- [ ] **T0-3**: Tailwind preset creation -- [ ] **T0-4**: Platform Context definition -- [ ] **T0-5**: Shared types migration - -### Phase 1: Pure UI Components Migration - -- [ ] **T1-1**: Icons components migration (8 files) -- [ ] **T1-2**: Tooltip component migration -- [ ] **T1-3**: WaitingMessage / InterruptedMessage migration -- [ ] **T1-4**: Basic Button/Input components refinement - -### Phase 2: Message Components Migration - -- [ ] **T2-1**: MarkdownRenderer migration -- [ ] **T2-2**: UserMessage migration -- [ ] **T2-3**: AssistantMessage migration -- [ ] **T2-4**: ThinkingMessage migration - -### Phase 3: Layout Components Migration - -- [ ] **T3-1**: ChatHeader migration -- [ ] **T3-2**: EmptyState migration -- [ ] **T3-3**: InputForm migration (requires Context) -- [ ] **T3-4**: SessionSelector migration -- [ ] **T3-5**: CompletionMenu migration - -### Phase 4: Complex Components Migration - -- [ ] **T4-1**: PermissionDrawer migration -- [ ] **T4-2**: ToolCall series components migration (16 files) - -### Phase 5: Platform Adapters - -- [ ] **T5-1**: VSCode Adapter implementation -- [ ] **T5-2**: Chrome Extension Adapter -- [ ] **T5-3**: Web/Share Page Adapter - ---- - -## 8. Risks and Considerations - -### 8.1 Common Pitfalls - -1. **Tailwind Class Name Tree Shaking** - - Problem: Tailwind class names may be removed after library bundling - - Solution: Consumer's `content` config needs to include `node_modules/@qwen-code/webui` - -2. **CSS Variable Scope** - - Problem: Variables like `var(--app-primary)` need to be defined by consumers - - Solution: Provide default CSS variables file, or define fallbacks in Tailwind preset - -3. **React Version Compatibility** - - Current vscode-ide-companion uses React 19, webui's peerDependencies is React 18 - - Need to update peerDependencies to `"react": "^18.0.0 || ^19.0.0"` - -4. **ESM/CJS Compatibility** - - VSCode extensions may require CJS format - - Vite needs to be configured for dual format output - -### 8.2 Industry References - -- **Radix UI**: Pure Headless components, styles completely controlled by consumers -- **shadcn/ui**: Copy components into project, rather than importing as dependency -- **Ant Design**: Complete component library, customization through ConfigProvider - -### 8.3 Acceptance Criteria - -Each migration task completion requires: - -1. Component has corresponding Storybook Story -2. Import in vscode-ide-companion has been updated -3. Extension builds successfully (`npm run build:vscode`) -4. Extension functionality works (manual testing or existing tests pass) - ---- - -## 9. Time Estimation - -| Phase | Tasks | Estimated Days | Parallelizable | -| ------- | ----- | -------------- | -------------- | -| Phase 0 | 5 | 2-3 days | Partially | -| Phase 1 | 4 | 1-2 days | Fully | -| Phase 2 | 4 | 2-3 days | Fully | -| Phase 3 | 5 | 3-4 days | Partially | -| Phase 4 | 2 | 3-4 days | Yes | -| Phase 5 | 3 | 2-3 days | Yes | - -**Total**: Approximately 13-19 person-days (sequential execution), can be reduced to 1-2 weeks with parallel work - ---- - -## 10. Development and Debugging Workflow - -### 10.1 Component Development Flow - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Development Workflow │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ 1. Develop/Modify Component │ -│ └── Edit files in @qwen-code/webui/src/ │ -│ │ -│ 2. Debug with Storybook │ -│ └── npm run storybook (port 6006) │ -│ └── View component in isolation │ -│ └── Test different props/states │ -│ │ -│ 3. Build Library │ -│ └── npm run build │ -│ └── Outputs: dist/index.js, dist/index.cjs, dist/index.d.ts │ -│ │ -│ 4. Use in VSCode Extension │ -│ └── import { Component } from '@qwen-code/webui' │ -│ └── No UI code modifications in vscode-ide-companion │ -│ │ -└─────────────────────────────────────────────────────────────────┘ -``` - -### 10.2 Debugging Commands - -```bash -# Start Storybook for component development -cd packages/webui -npm run storybook - -# Watch mode for library development -npm run dev - -# Build library for production -npm run build - -# Type checking -npm run typecheck -``` - -### 10.3 Key Principles - -1. **Single Source of Truth**: All UI components live in `@qwen-code/webui` -2. **Storybook First**: Debug and validate components in Storybook before integration -3. **No UI Code in Consumers**: `vscode-ide-companion` only imports and uses components -4. **Platform Abstraction**: Use `PlatformContext` for platform-specific behaviors diff --git a/packages/webui/examples/README.md b/packages/webui/examples/README.md deleted file mode 100644 index dedd708b74d..00000000000 --- a/packages/webui/examples/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Examples - -This directory contains example implementations demonstrating various ways to use the `@qwen-code/webui` library. - -## Available Examples - -- [`cdn-usage-demo.html`](./cdn-usage-demo.html) - Shows how to use the library directly via CDN in a browser environment, featuring the ChatViewer component -- [`complex-chat-demo.html`](./complex-chat-demo.html) - Demonstrates a complex chat conversation with tool calls, code blocks, and mixed-language interactions diff --git a/packages/webui/examples/cdn-usage-demo.html b/packages/webui/examples/cdn-usage-demo.html deleted file mode 100644 index 2919614af54..00000000000 --- a/packages/webui/examples/cdn-usage-demo.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - @qwen-code/webui CDN Usage Example - - - - - - - - - - - - - - - - - -
-

@qwen-code/webui CDN Usage Example

-

ChatViewer Component Demo

-
-
- - - - diff --git a/packages/webui/examples/complex-chat-demo.html b/packages/webui/examples/complex-chat-demo.html deleted file mode 100644 index 22adbc3dd2f..00000000000 --- a/packages/webui/examples/complex-chat-demo.html +++ /dev/null @@ -1,428 +0,0 @@ - - - - - - @qwen-code/webui Complex Chat Demo - - - - - - - - - - - - - - - - - - - -
-

@qwen-code/webui Complex Chat Demo

-

Real conversation example with tool calls

-
- -

Alternative: With Full Tailwind Support

-

- For full Tailwind utility class support (like gap-1.5, button classes, - etc.), also include: -

-
<script src="https://cdn.tailwindcss.com"></script>
-
- - - - diff --git a/packages/webui/package.json b/packages/webui/package.json deleted file mode 100644 index b2232eeeb8b..00000000000 --- a/packages/webui/package.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "name": "@qwen-code/webui", - "version": "0.23.0", - "description": "Shared UI components for Qwen Code packages", - "type": "module", - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "./icons": { - "types": "./dist/components/icons/index.d.ts", - "import": "./dist/components/icons/index.js", - "require": "./dist/components/icons/index.cjs" - }, - "./tailwind.preset": "./tailwind.preset.cjs", - "./styles.css": "./dist/styles.css" - }, - "files": [ - "dist", - "tailwind.preset.cjs" - ], - "sideEffects": [ - "**/*.css" - ], - "publishConfig": { - "access": "public" - }, - "scripts": { - "dev": "vite build --watch", - "build": "vite build", - "test": "vitest run", - "test:ci": "vitest run", - "typecheck": "tsc --noEmit", - "lint": "eslint src --ext .ts,.tsx", - "lint:fix": "eslint src --ext .ts,.tsx --fix", - "storybook": "storybook dev -p 6006", - "build-storybook": "storybook build" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "dependencies": { - "@qwen-code/sdk": "file:../sdk-typescript", - "markdown-it": "^14.1.0" - }, - "devDependencies": { - "@types/markdown-it": "^14.1.2", - "@types/react": "^19.1.8", - "@types/react-dom": "^19.1.6", - "@vitejs/plugin-react": "^4.2.0", - "autoprefixer": "^10.4.0", - "postcss": "^8.4.0", - "tailwindcss": "^3.4.0", - "typescript": "^5.0.0", - "vite": "^5.0.0", - "vitest": "^3.2.4", - "vite-plugin-dts": "^4.5.4", - "storybook": "^10.1.11", - "@storybook/react-vite": "^10.1.11", - "@chromatic-com/storybook": "^5.0.0", - "@storybook/addon-vitest": "^10.1.11", - "@storybook/addon-a11y": "^10.1.11", - "@storybook/addon-docs": "^10.1.11", - "@storybook/addon-onboarding": "^10.1.11", - "eslint-plugin-storybook": "^10.1.11", - "playwright": "^1.57.0", - "@vitest/browser": "^3.2.4", - "@vitest/coverage-v8": "^3.2.4" - }, - "keywords": [ - "qwen", - "ui", - "components", - "shared" - ], - "author": "Qwen Team", - "license": "MIT" -} diff --git a/packages/webui/postcss.config.cjs b/packages/webui/postcss.config.cjs deleted file mode 100644 index 29fd6d75deb..00000000000 --- a/packages/webui/postcss.config.cjs +++ /dev/null @@ -1,13 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -/* eslint-env node */ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/packages/webui/scripts/add-license-header.sh b/packages/webui/scripts/add-license-header.sh deleted file mode 100755 index 8fa424202b8..00000000000 --- a/packages/webui/scripts/add-license-header.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -# Script to check and add license header to files in the packages/webui directory -# If a file doesn't have the required license header, it will be added at the top -# Excludes Markdown files and common build/dependency directories - -LICENSE_HEADER="/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */" - -# Directory to scan (relative to script location) -TARGET_DIR="$(dirname "$0")/../" - -# Find all JavaScript, TypeScript, CSS, HTML, and JSX/TSX files in the target directory, excluding Markdown files -# Also exclude common build/dependency directories -find "$TARGET_DIR" -type f \( -name "*.js" -o -name "*.jsx" -o -name "*.ts" -o -name "*.tsx" -o -name "*.cjs" -o -name "*.mjs" -o -name "*.css" -o -name "*.html" \) -not -name "*.md" \ - -not -path "*/node_modules/*" \ - -not -path "*/dist/*" \ - -not -path "*/build/*" \ - -not -path "*/coverage/*" \ - -not -path "*/.next/*" \ - -not -path "*/out/*" \ - -not -path "*/target/*" \ - -not -path "*/vendor/*" \ - -print0 | while IFS= read -r -d '' file; do - # Skip the script file itself - if [[ "$(basename "$file")" != "add-license-header.sh" ]]; then - # Check if the file starts with the license header - if ! head -n 5 "$file" | grep -Fq "@license"; then - echo "Adding license header to: $file" - - # Create a temporary file with the license header followed by the original content - temp_file=$(mktemp) - echo "$LICENSE_HEADER" > "$temp_file" - echo "" >> "$temp_file" # Add an empty line after the license header - cat "$file" >> "$temp_file" - - # Move the temporary file to replace the original file - mv "$temp_file" "$file" - else - echo "License header already present in: $file" - fi - fi -done - -echo "License header check and update completed." \ No newline at end of file diff --git a/packages/webui/src/adapters/ACPAdapter.ts b/packages/webui/src/adapters/ACPAdapter.ts deleted file mode 100644 index eaf699e1ba7..00000000000 --- a/packages/webui/src/adapters/ACPAdapter.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Adapter for ACP protocol messages (used by vscode-ide-companion) - */ - -import type { - UnifiedMessage, - ACPMessage, - ACPMessageData, - ToolCallData, -} from './types.js'; - -/** - * Check if a message is a user message (breaks AI sequence) - */ -function isUserMessage(msg: ACPMessage | undefined): boolean { - if (!msg) return true; - if (msg.type !== 'message') return false; - const data = msg.data as ACPMessageData; - return data?.role === 'user'; -} - -/** - * Adapt ACP messages to unified format - * - * @param messages - Array of ACP messages from vscode-ide-companion - * @returns Array of unified messages with timeline positions calculated - */ -export function adaptACPMessages(messages: ACPMessage[]): UnifiedMessage[] { - return messages.map((item, index, arr) => { - const prev = arr[index - 1]; - const next = arr[index + 1]; - - // Calculate timeline position - const isFirst = isUserMessage(prev); - const isLast = isUserMessage(next); - - switch (item.type) { - case 'message': { - const msg = item.data as ACPMessageData; - return { - id: `msg-${index}`, - type: - msg.role === 'user' - ? 'user' - : msg.role === 'thinking' - ? 'thinking' - : 'assistant', - timestamp: msg.timestamp || Date.now(), - content: msg.content, - fileContext: msg.fileContext, - isFirst, - isLast, - }; - } - - case 'in-progress-tool-call': - case 'completed-tool-call': { - const toolCall = item.data as ToolCallData; - return { - id: `tool-${toolCall.toolCallId}-${item.type}`, - type: 'tool_call', - timestamp: Date.now(), - toolCall, - isFirst, - isLast, - }; - } - - default: - // Fallback for unknown types - return { - id: `unknown-${index}`, - type: 'assistant', - timestamp: Date.now(), - content: '', - isFirst, - isLast, - }; - } - }); -} - -/** - * Type guard to check if data is a tool call - */ -export function isToolCallData(data: unknown): data is ToolCallData { - return ( - typeof data === 'object' && - data !== null && - 'toolCallId' in data && - 'kind' in data - ); -} - -/** - * Type guard to check if data is a message - */ -export function isMessageData(data: unknown): data is ACPMessageData { - return ( - typeof data === 'object' && - data !== null && - 'role' in data && - 'content' in data - ); -} diff --git a/packages/webui/src/adapters/JSONLAdapter.test.ts b/packages/webui/src/adapters/JSONLAdapter.test.ts deleted file mode 100644 index 08a8c3adb72..00000000000 --- a/packages/webui/src/adapters/JSONLAdapter.test.ts +++ /dev/null @@ -1,149 +0,0 @@ -/** - * @license - * Copyright 2026 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import { describe, expect, it } from 'vitest'; -import { adaptJSONLMessages } from './JSONLAdapter.js'; -import type { JSONLMessage } from './types.js'; - -function userMessage( - parts: Array<{ text: string }>, - systemPayload?: unknown, -): JSONLMessage { - return { - uuid: 'user-1', - timestamp: '2026-07-28T00:00:00.000Z', - type: 'user', - message: { role: 'user', parts }, - ...(systemPayload === undefined ? {} : { systemPayload }), - }; -} - -describe('adaptJSONLMessages user display projection', () => { - it('uses display metadata without exposing model-only parts', () => { - const [message] = adaptJSONLMessages([ - userMessage( - [ - { text: 'expanded model prompt' }, - { - text: [ - '', - 'hook-only context', - '', - ].join('\n'), - }, - ], - { - displayText: 'raw @file prompt', - hookContext: 'hook-only context', - }, - ), - ]); - - expect(message?.content).toBe('raw @file prompt'); - }); - - it('treats empty paired displayText as meaningful', () => { - const [message] = adaptJSONLMessages([ - userMessage([{ text: 'expanded model prompt' }], { - displayText: '', - hookContext: 'hook-only context', - }), - ]); - - expect(message?.content).toBe(''); - }); - - it('keeps notification model text instead of its display label', () => { - const [message] = adaptJSONLMessages([ - userMessage([{ text: 'notification model text' }], { - displayText: 'Background agent completed', - }), - ]); - - expect(message?.content).toBe('notification model text'); - }); - - it('strips a complete final tag-only context part', () => { - const [message] = adaptJSONLMessages([ - userMessage([ - { text: 'user prompt' }, - { - text: [ - '', - 'hook-only context', - '', - ].join('\n'), - }, - ]), - ]); - - expect(message?.content).toBe('user prompt'); - }); - - it('strips a final tag-only context part with invalid metadata', () => { - const [message] = adaptJSONLMessages([ - userMessage( - [ - { text: 'user prompt' }, - { - text: [ - '', - 'hook-only context', - '', - ].join('\n'), - }, - ], - null, - ), - ]); - - expect(message?.content).toBe('user prompt'); - }); - - it('preserves legacy bare-part concatenation without a reliable boundary', () => { - const [message] = adaptJSONLMessages([ - userMessage([ - { text: 'user prompt' }, - { text: 'legacy bare hook context' }, - ]), - ]); - - expect(message?.content).toBe('user promptlegacy bare hook context'); - }); - - it('uses released single-field display metadata when the final tag proves provenance', () => { - const [message] = adaptJSONLMessages([ - userMessage( - [ - { text: 'user prompt' }, - { - text: [ - '', - 'user-authored text', - '', - ].join('\n'), - }, - ], - { displayText: 'raw @file prompt' }, - ), - ]); - - expect(message?.content).toBe('raw @file prompt'); - }); - - it('leaves non-Qwen user records to the existing format parser', () => { - const [message] = adaptJSONLMessages([ - { - uuid: 'claude-user-1', - timestamp: '2026-07-28T00:00:00.000Z', - type: 'user', - message: { role: 'user', content: 'Claude user prompt' }, - }, - ]); - - expect(message?.content).toBe('Claude user prompt'); - }); -}); diff --git a/packages/webui/src/adapters/JSONLAdapter.ts b/packages/webui/src/adapters/JSONLAdapter.ts deleted file mode 100644 index 62938c99bf9..00000000000 --- a/packages/webui/src/adapters/JSONLAdapter.ts +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Adapter for JSONL format messages (used by ChatViewer) - */ - -import type { - UnifiedMessage, - JSONLMessage, - UnifiedMessageType, -} from './types.js'; -import { getUserTranscriptDisplayText } from './userTranscriptDisplay.js'; - -/** - * Extract text content from different message formats - */ -function extractContent(message?: { - parts?: Array<{ text: string }>; - content?: string | unknown[]; -}): string { - if (!message) return ''; - - // Qwen format: parts array - if (message.parts?.length) { - return message.parts.map((p) => p.text).join(''); - } - - // Claude format: string content - if (typeof message.content === 'string') { - return message.content; - } - - // Claude format: content array - if (Array.isArray(message.content)) { - return message.content - .filter( - (item): item is { type: 'text'; text: string } => - typeof item === 'object' && - item !== null && - 'type' in item && - item.type === 'text', - ) - .map((item) => item.text) - .join(''); - } - - return ''; -} - -/** - * Parse timestamp string to milliseconds - */ -function parseTimestamp(timestamp: string): number { - const parsed = Date.parse(timestamp); - return isNaN(parsed) ? Date.now() : parsed; -} - -/** - * Determine the unified message type from JSONL message - */ -function getMessageType(msg: JSONLMessage): UnifiedMessageType { - if (msg.type === 'tool_call') { - return 'tool_call'; - } - if (msg.type === 'user') { - return 'user'; - } - if (msg.message?.role === 'thinking') { - return 'thinking'; - } - return 'assistant'; -} - -/** - * Check if a message is a user type (breaks AI sequence) - */ -function isUserType(msg: JSONLMessage | undefined): boolean { - return !msg || msg.type === 'user'; -} - -/** - * Adapt JSONL messages to unified format - * - * @param messages - Array of JSONL messages - * @returns Array of unified messages with timeline positions calculated - */ -export function adaptJSONLMessages(messages: JSONLMessage[]): UnifiedMessage[] { - // Sort by timestamp - const sorted = [...messages].sort( - (a, b) => parseTimestamp(a.timestamp) - parseTimestamp(b.timestamp), - ); - - return sorted.map((msg, index, arr) => { - const prev = arr[index - 1]; - const next = arr[index + 1]; - - // Calculate timeline position - const isFirst = isUserType(prev); - const isLast = isUserType(next); - - const type = getMessageType(msg); - const userContent = getUserTranscriptDisplayText(msg); - - return { - id: msg.uuid, - type, - timestamp: parseTimestamp(msg.timestamp), - content: - type !== 'tool_call' - ? (userContent ?? extractContent(msg.message)) - : undefined, - toolCall: msg.toolCall, - isFirst, - isLast, - }; - }); -} - -/** - * Filter out empty messages (except tool calls) - */ -export function filterEmptyMessages( - messages: UnifiedMessage[], -): UnifiedMessage[] { - return messages.filter((msg) => { - if (msg.type === 'tool_call') return true; - return msg.content && msg.content.trim().length > 0; - }); -} diff --git a/packages/webui/src/adapters/index.ts b/packages/webui/src/adapters/index.ts deleted file mode 100644 index acacb3e654b..00000000000 --- a/packages/webui/src/adapters/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Adapter layer for normalizing different data formats to unified message format - */ - -// Type exports -export type { - UnifiedMessage, - UnifiedMessageType, - JSONLMessage, - ACPMessage, - ACPMessageData, - ToolCallData, - FileContext, -} from './types.js'; - -// JSONL Adapter (for ChatViewer) -export { adaptJSONLMessages, filterEmptyMessages } from './JSONLAdapter.js'; - -// ACP Adapter (for vscode-ide-companion) -export { - adaptACPMessages, - isToolCallData, - isMessageData, -} from './ACPAdapter.js'; diff --git a/packages/webui/src/adapters/types.ts b/packages/webui/src/adapters/types.ts deleted file mode 100644 index b88ddc45d87..00000000000 --- a/packages/webui/src/adapters/types.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * Unified message types for adapter layer - */ - -import type { ToolCallData } from '../components/toolcalls/shared/types.js'; -import type { FileContext } from '../components/messages/UserMessage.js'; - -/** - * Unified message type used by all webui components - */ -export type UnifiedMessageType = - | 'user' - | 'assistant' - | 'tool_call' - | 'thinking'; - -/** - * Unified message format - normalized from ACP or JSONL sources - */ -export interface UnifiedMessage { - /** Unique identifier */ - id: string; - /** Message type */ - type: UnifiedMessageType; - /** Timestamp in milliseconds */ - timestamp: number; - /** Text content (for user/assistant/thinking messages) */ - content?: string; - /** Tool call data (for tool_call type) */ - toolCall?: ToolCallData; - /** Whether this is the first item in an AI response sequence */ - isFirst: boolean; - /** Whether this is the last item in an AI response sequence */ - isLast: boolean; - /** File context for user messages */ - fileContext?: FileContext[]; -} - -// Re-export FileContext for convenience -export type { FileContext }; - -/** - * JSONL chat message format (ChatViewer input) - */ -export interface JSONLMessage { - uuid: string; - parentUuid?: string | null; - sessionId?: string; - timestamp: string; // ISO timestamp string - type: 'user' | 'assistant' | 'system' | 'tool_call'; - message?: { - role?: string; - parts?: Array<{ text: string }>; // Qwen format - content?: string | unknown[]; // Claude format - }; - systemPayload?: unknown; - model?: string; - toolCall?: ToolCallData; -} - -/** - * ACP message format (vscode-ide-companion input) - */ -export interface ACPMessage { - type: 'message' | 'in-progress-tool-call' | 'completed-tool-call'; - data: ACPMessageData | ToolCallData; -} - -/** - * ACP text message data - */ -export interface ACPMessageData { - role: 'user' | 'assistant' | 'thinking'; - content: string; - timestamp?: number; - fileContext?: FileContext[]; -} - -export type { ToolCallData }; diff --git a/packages/webui/src/adapters/userTranscriptDisplay.ts b/packages/webui/src/adapters/userTranscriptDisplay.ts deleted file mode 100644 index d168e5993a0..00000000000 --- a/packages/webui/src/adapters/userTranscriptDisplay.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * @license - * Copyright 2026 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -// Keep these in sync with USER_PROMPT_SUBMIT_CONTEXT_OPEN/CLOSE in core. -const USER_PROMPT_CONTEXT_OPEN = ''; -const USER_PROMPT_CONTEXT_CLOSE = ''; - -type UserTranscriptRecord = { - type?: unknown; - message?: { - parts?: readonly unknown[]; - }; - systemPayload?: unknown; -}; - -function isRecord(value: unknown): value is Record { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function isHookContextPart(part: unknown): boolean { - if (!isRecord(part) || typeof part.text !== 'string') return false; - const text = part.text.trim(); - const prefix = `${USER_PROMPT_CONTEXT_OPEN}\n`; - const suffix = `\n${USER_PROMPT_CONTEXT_CLOSE}`; - if (!text.startsWith(prefix) || !text.endsWith(suffix)) { - return false; - } - const body = text.slice(prefix.length, -suffix.length); - return ( - !body.includes(USER_PROMPT_CONTEXT_OPEN) && - !body.includes(USER_PROMPT_CONTEXT_CLOSE) - ); -} - -/** - * Returns a user record's clean text projection. `undefined` means the record - * is not a Qwen user record and the caller should use its other format parser. - */ -export function getUserTranscriptDisplayText( - record: UserTranscriptRecord, -): string | undefined { - if (record.type !== 'user') return undefined; - - const parts = Array.isArray(record.message?.parts) - ? record.message.parts - : []; - const hasFinalHookContextPart = - parts.length > 1 && isHookContextPart(parts[parts.length - 1]); - const payload = isRecord(record.systemPayload) - ? record.systemPayload - : undefined; - if ( - payload && - typeof payload.displayText === 'string' && - (typeof payload.hookContext === 'string' || hasFinalHookContextPart) - ) { - return payload.displayText; - } - - if (parts.length === 0) return undefined; - const visibleParts = - payload === undefined && hasFinalHookContextPart - ? parts.slice(0, -1) - : parts; - return visibleParts - .map((part) => - isRecord(part) && typeof part.text === 'string' ? part.text : '', - ) - .join(''); -} diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.css b/packages/webui/src/components/ChatViewer/ChatViewer.css deleted file mode 100644 index 82a1b431cfc..00000000000 --- a/packages/webui/src/components/ChatViewer/ChatViewer.css +++ /dev/null @@ -1,230 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - * - * ChatViewer component styles - matching vscode-ide-companion visual appearance - * Note: Timeline styles are inherited from shared styles/timeline.css - */ - -/* =========================== - Main Chat Viewer Container - =========================== */ -.chat-viewer-container { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - background-color: var( - --app-background, - var(--app-primary-background, #1e1e1e) - ); - color: var(--app-primary-foreground, #cccccc); - font-family: var( - --vscode-chat-font-family, - var( - --vscode-font-family, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Roboto, - sans-serif - ) - ); - font-size: var(--vscode-chat-font-size, 13px); - overflow: hidden; -} - -/* =========================== - Messages Container (scrollable) - =========================== */ -.chat-viewer-messages { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - padding: 20px; - display: flex; - flex-direction: column; - position: relative; - min-width: 0; - /* Enable smooth scrolling for auto-scroll */ - scroll-behavior: smooth; -} - -/* =========================== - Global expand/collapse control - =========================== */ -.chat-viewer-expand-control { - display: flex; - justify-content: flex-end; - gap: 8px; - padding: 10px 20px 0; - flex-shrink: 0; -} - -.chat-viewer-expand-control-button { - background: transparent; - border: 1px solid var(--app-input-border, rgba(255, 255, 255, 0.2)); - border-radius: 4px; - color: var(--app-secondary-foreground, rgba(255, 255, 255, 0.6)); - cursor: pointer; - font-size: 11px; - line-height: 1.4; - padding: 3px 10px; - transition: - color 0.15s ease, - border-color 0.15s ease, - background-color 0.15s ease; -} - -.chat-viewer-expand-control-button:hover { - color: var(--app-primary-foreground, #cccccc); - border-color: var(--app-primary-foreground, #cccccc); - background-color: var(--app-input-background, rgba(255, 255, 255, 0.06)); -} - -@media (max-width: 600px) { - .chat-viewer-expand-control { - padding: 8px 12px 0; - } -} - -/* Dark theme scrollbar styling */ -.chat-viewer-messages::-webkit-scrollbar { - width: 8px; -} - -.chat-viewer-messages::-webkit-scrollbar-track { - background: transparent; -} - -.chat-viewer-messages::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.2); - border-radius: 4px; -} - -.chat-viewer-messages::-webkit-scrollbar-thumb:hover { - background: rgba(255, 255, 255, 0.3); -} - -/* Light theme scrollbar styling */ -@media (prefers-color-scheme: light) { - .chat-viewer-container.auto-theme - .chat-viewer-messages::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); - } - - .chat-viewer-container.auto-theme - .chat-viewer-messages::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.3); - } -} - -/* Force light theme scrollbar */ -.chat-viewer-container.light-theme - .chat-viewer-messages::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); -} - -.chat-viewer-container.light-theme - .chat-viewer-messages::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.3); -} - -/* Message item base styles */ -.chat-viewer-messages > * { - display: flex; - gap: 0; - align-items: flex-start; - text-align: left; - padding-top: 8px; - padding-bottom: 8px; - flex-direction: column; - position: relative; - animation: chatViewerFadeIn 0.2s ease-in; -} - -.chat-viewer-messages > .chat-viewer-scroll-anchor { - padding: 0; - display: block; -} - -/* Disable overflow anchoring on individual items for manual scroll control */ -.chat-viewer-messages > * { - overflow-anchor: none; -} - -/* User message container spacing */ -.chat-viewer-messages .user-message-container:first-child { - margin-top: 0; -} - -/* =========================== - Animations - =========================== */ -@keyframes chatViewerFadeIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* =========================== - Empty State - =========================== */ -.chat-viewer-empty { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - min-height: 200px; - color: var(--app-secondary-foreground, rgba(255, 255, 255, 0.6)); - font-size: 14px; - text-align: center; - padding: 20px; -} - -.chat-viewer-empty-icon { - font-size: 48px; - margin-bottom: 16px; - opacity: 0.5; -} - -.chat-viewer-empty-text { - max-width: 300px; - line-height: 1.5; -} - -/* =========================== - Scroll Anchor (for auto-scroll) - =========================== */ -.chat-viewer-scroll-anchor { - height: 1px; - overflow-anchor: auto; -} - -/* =========================== - ChatViewer-specific Styles - =========================== */ - -/* Better spacing between message groups */ -.chat-viewer-messages .user-message-container { - margin-top: 16px; -} - -/* Ensure proper stacking context */ -.chat-viewer-messages > * { - isolation: isolate; -} - -/* Responsive adjustments for ChatViewer */ -@media (max-width: 600px) { - .chat-viewer-messages { - padding: 12px; - } -} diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.expandControl.test.tsx b/packages/webui/src/components/ChatViewer/ChatViewer.expandControl.test.tsx deleted file mode 100644 index ca91cbca3f8..00000000000 --- a/packages/webui/src/components/ChatViewer/ChatViewer.expandControl.test.tsx +++ /dev/null @@ -1,462 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -// @vitest-environment jsdom - -import { act } from 'react'; -import { createRoot, type Root } from 'react-dom/client'; -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import { ChatViewer, type ChatMessageData } from './ChatViewer.js'; - -const LONG_OUTPUT = `${'x'.repeat(600)}__SHELL_TAIL__`; - -const LONG_READ_OUTPUT = `${'y'.repeat(400)}__READ_TAIL__`; - -const USER_MESSAGE_WITH_FILE_REFERENCE = [ - 'please review this file', - '--- Content from referenced files ---', - 'Content from @src/example.ts:', - 'FILE-REF-BODY-LINE-1', - 'FILE-REF-BODY-LINE-2', - '--- End of content ---', -].join('\n'); - -const createFileAndReadMessages = (): ChatMessageData[] => [ - { - uuid: 'user-file-1', - timestamp: '2026-03-22T16:48:30.000Z', - type: 'user', - message: { - role: 'user', - parts: [{ text: USER_MESSAGE_WITH_FILE_REFERENCE }], - }, - }, - { - uuid: 'read-1', - timestamp: '2026-03-22T16:48:35.000Z', - type: 'tool_call', - toolCall: { - toolCallId: 'read-1-call', - kind: 'read', - title: 'Read src/example.ts', - status: 'completed', - rawInput: { file_path: 'src/example.ts' }, - locations: [{ path: 'src/example.ts' }], - content: [ - { - type: 'content', - content: { type: 'text', text: LONG_READ_OUTPUT }, - }, - ], - }, - }, -]; - -const LONG_SEARCH_OUTPUT = `${'s'.repeat(500)}SEARCH-TAIL-MARKER`; - -const LONG_FETCH_OUTPUT = `${'f'.repeat(400)} WEBFETCH-TAIL-MARKER`; - -const createSearchMessages = (): ChatMessageData[] => [ - { - uuid: 'user-search', - timestamp: '2026-03-22T16:48:30.000Z', - type: 'user', - message: { role: 'user', parts: [{ text: 'find the thing' }] }, - }, - { - uuid: 'search-1', - timestamp: '2026-03-22T16:48:35.000Z', - type: 'tool_call', - toolCall: { - toolCallId: 'search-1-call', - kind: 'search', - title: 'Search "pattern"', - status: 'completed', - rawInput: { pattern: 'pattern' }, - content: [ - { - type: 'content', - content: { type: 'text', text: LONG_SEARCH_OUTPUT }, - }, - ], - }, - }, -]; - -const createWebFetchMessages = (): ChatMessageData[] => [ - { - uuid: 'user-fetch', - timestamp: '2026-03-22T16:48:30.000Z', - type: 'user', - message: { role: 'user', parts: [{ text: 'fetch the page' }] }, - }, - { - uuid: 'fetch-1', - timestamp: '2026-03-22T16:48:35.000Z', - type: 'tool_call', - toolCall: { - toolCallId: 'fetch-1-call', - kind: 'web_fetch', - title: 'Fetch https://example.com', - status: 'completed', - rawInput: { url: 'https://example.com' }, - content: [ - { - type: 'content', - content: { type: 'text', text: LONG_FETCH_OUTPUT }, - }, - ], - }, - }, -]; - -const createMessages = (): ChatMessageData[] => [ - { - uuid: 'user-1', - timestamp: '2026-03-22T16:48:30.000Z', - type: 'user', - message: { role: 'user', parts: [{ text: 'hello' }] }, - }, - { - uuid: 'thinking-1', - timestamp: '2026-03-22T16:48:31.000Z', - type: 'assistant', - message: { - role: 'thinking', - // Avoid `__text__` markers: thinking content renders as Markdown and - // would strip the double underscores as bold syntax. - parts: [{ text: 'thinking body THINKING-BODY-MARKER' }], - }, - }, - { - uuid: 'shell-1', - timestamp: '2026-03-22T16:48:35.000Z', - type: 'tool_call', - toolCall: { - toolCallId: 'shell-1-call', - kind: 'bash', - title: 'Run command', - status: 'completed', - rawInput: { command: 'run-command' }, - content: [ - { type: 'content', content: { type: 'text', text: LONG_OUTPUT } }, - ], - }, - }, -]; - -describe('ChatViewer global expand control', () => { - let container: HTMLDivElement | null = null; - let root: Root | null = null; - - beforeEach(() => { - ( - globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean } - ).IS_REACT_ACT_ENVIRONMENT = true; - container = document.createElement('div'); - document.body.appendChild(container); - root = createRoot(container); - }); - - afterEach(() => { - if (root) { - act(() => root?.unmount()); - root = null; - } - if (container) { - container.remove(); - container = null; - } - }); - - const clickButton = (label: string) => { - const button = container?.querySelector( - `button[aria-label="${label}"]`, - ) as HTMLButtonElement; - expect(button).not.toBeNull(); - act(() => { - button.dispatchEvent(new MouseEvent('click', { bubbles: true })); - }); - }; - - const getOutputToggle = () => - container?.querySelector( - 'button[aria-label="Expand output"], button[aria-label="Collapse output"]', - ) as HTMLButtonElement; - - const getOutputContent = () => - container?.querySelector( - '.toolcall-collapsible-output-content', - ) as HTMLDivElement; - - const getOutputCardContent = () => - container?.querySelector( - '.webfetch-output-content', - ) as HTMLDivElement | null; - - const isThinkingExpanded = () => - container?.querySelector('.thinking-content') != null; - - it('does not render the toolbar by default', () => { - act(() => { - root?.render( - , - ); - }); - - expect(container?.querySelector('.chat-viewer-expand-control')).toBeNull(); - }); - - it('expands thinking blocks and long tool outputs via Expand all', async () => { - act(() => { - root?.render( - , - ); - }); - - // Baseline: both sections start collapsed. ShellToolCall renders its - // output with collapsedHeight=60 (not the CollapsibleOutput default). - expect(isThinkingExpanded()).toBe(false); - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('false'); - expect(getOutputContent().style.maxHeight).toBe('60px'); - - clickButton('Expand all sections'); - - // Wait for the state instead of reading it synchronously so the - // assertions stay deterministic under degraded scheduling (cold-cache - // CI runs can defer the global signal flush past the click's act()). - await vi.waitFor(() => { - expect(isThinkingExpanded()).toBe(true); - expect(container?.textContent).toContain('THINKING-BODY-MARKER'); - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('true'); - expect(getOutputContent().style.maxHeight).toBe(''); - }); - }); - - it('collapses previously expanded sections via Collapse all', async () => { - act(() => { - root?.render( - , - ); - }); - - clickButton('Expand all sections'); - await vi.waitFor(() => expect(isThinkingExpanded()).toBe(true)); - - clickButton('Collapse all sections'); - - await vi.waitFor(() => { - expect(isThinkingExpanded()).toBe(false); - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('false'); - expect(getOutputContent().style.maxHeight).toBe('60px'); - }); - }); - - it('keeps individual toggles working after a global command', async () => { - act(() => { - root?.render( - , - ); - }); - - clickButton('Expand all sections'); - await vi.waitFor(() => expect(isThinkingExpanded()).toBe(true)); - - // Manually collapse the thinking block again. - const thinkingToggle = container?.querySelector( - 'button[aria-label="Collapse thinking"]', - ) as HTMLButtonElement; - expect(thinkingToggle).not.toBeNull(); - act(() => { - thinkingToggle.dispatchEvent(new MouseEvent('click', { bubbles: true })); - }); - expect(isThinkingExpanded()).toBe(false); - - // The shell output stays expanded: the manual toggle only affects its - // own section. - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('true'); - - // A later global command reaches every section again. - clickButton('Collapse all sections'); - await vi.waitFor(() => - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('false'), - ); - }); - - it('does not render the toolbar for an empty message list', () => { - act(() => { - root?.render( - , - ); - }); - - expect(container?.querySelector('.chat-viewer-expand-control')).toBeNull(); - }); - - it('expands file references and read outputs via the global buttons', async () => { - act(() => { - root?.render( - , - ); - }); - - const getFileRefToggle = () => - container?.querySelector( - '.user-message-container button[aria-expanded]', - ) as HTMLButtonElement; - const getReadToggle = () => - container?.querySelector( - '.read-tool-call-success button[aria-expanded]', - ) as HTMLButtonElement; - - // Baseline: both sections start collapsed. The collapsed file-reference - // body is not rendered at all. - expect(getFileRefToggle().getAttribute('aria-expanded')).toBe('false'); - expect(container?.textContent).not.toContain('FILE-REF-BODY-LINE-1'); - expect(getReadToggle().getAttribute('aria-expanded')).toBe('false'); - expect(getReadToggle()?.textContent).toContain('Show more'); - - clickButton('Expand all sections'); - - await vi.waitFor(() => { - expect(getFileRefToggle().getAttribute('aria-expanded')).toBe('true'); - expect(container?.textContent).toContain('FILE-REF-BODY-LINE-1'); - expect(getReadToggle().getAttribute('aria-expanded')).toBe('true'); - expect(getReadToggle()?.textContent).toContain('Collapse'); - }); - - clickButton('Collapse all sections'); - - await vi.waitFor(() => { - expect(getFileRefToggle().getAttribute('aria-expanded')).toBe('false'); - expect(container?.textContent).not.toContain('FILE-REF-BODY-LINE-1'); - expect(getReadToggle().getAttribute('aria-expanded')).toBe('false'); - expect(getReadToggle()?.textContent).toContain('Show more'); - }); - }); - - it('mounts sections added after a global command in the target state', async () => { - act(() => { - root?.render( - , - ); - }); - - clickButton('Expand all sections'); - await vi.waitFor(() => - expect(container?.querySelectorAll('.thinking-content').length).toBe(1), - ); - - // A new thinking message arrives after the global command (streaming). - const lateThinking: ChatMessageData = { - uuid: 'thinking-late', - timestamp: '2026-03-22T16:49:00.000Z', - type: 'assistant', - message: { - role: 'thinking', - parts: [{ text: 'late body LATE-THINKING-MARKER' }], - }, - }; - act(() => { - root?.render( - , - ); - }); - - // The late section inherits the latest global target (expanded) - // without another toolbar click. - expect(container?.querySelectorAll('.thinking-content').length).toBe(2); - expect(container?.textContent).toContain('LATE-THINKING-MARKER'); - }); - - it('toggles SearchToolCall collapsible output via the global buttons', async () => { - act(() => { - root?.render( - , - ); - }); - - // Baseline: the search result body starts collapsed (its content is not - // rendered until expanded), and its trigger is a keyboard-reachable - // button with expanded-state semantics. - const searchToggle = container?.querySelector( - '[role="button"][aria-expanded]', - ) as HTMLElement; - expect(searchToggle).not.toBeNull(); - expect(searchToggle.getAttribute('aria-expanded')).toBe('false'); - expect(searchToggle.tabIndex).toBe(0); - expect(container?.textContent).not.toContain('SEARCH-TAIL-MARKER'); - - clickButton('Expand all sections'); - await vi.waitFor(() => - expect(container?.textContent).toContain('SEARCH-TAIL-MARKER'), - ); - - clickButton('Collapse all sections'); - await vi.waitFor(() => - expect(container?.textContent).not.toContain('SEARCH-TAIL-MARKER'), - ); - }); - - it('toggles WebFetchToolCall OutputCard via the global buttons', async () => { - act(() => { - root?.render( - , - ); - }); - - // Baseline: long content (> EXPAND_THRESHOLD) renders the toggle with - // aria wiring and a collapsed max-height. - const toggle = getOutputToggle(); - expect(toggle).not.toBeNull(); - expect(toggle.getAttribute('aria-expanded')).toBe('false'); - expect(getOutputCardContent()?.style.maxHeight).toBe('120px'); - - clickButton('Expand all sections'); - await vi.waitFor(() => { - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('true'); - expect(getOutputCardContent()?.style.maxHeight).toBe(''); - }); - - clickButton('Collapse all sections'); - await vi.waitFor(() => { - expect(getOutputToggle().getAttribute('aria-expanded')).toBe('false'); - expect(getOutputCardContent()?.style.maxHeight).toBe('120px'); - }); - }); -}); diff --git a/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx b/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx deleted file mode 100644 index 894a04a5520..00000000000 --- a/packages/webui/src/components/ChatViewer/ChatViewer.stories.tsx +++ /dev/null @@ -1,1650 +0,0 @@ -/** - * @license - * Copyright 2025 Qwen Team - * SPDX-License-Identifier: Apache-2.0 - */ - -import { useRef, useState, useCallback, useEffect } from 'react'; -import type { Meta, StoryObj } from '@storybook/react-vite'; -import { - ChatViewer, - type ChatMessageData, - type ChatViewerHandle, - type ToolCallData, -} from './ChatViewer.js'; - -/** - * ChatViewer component displays a read-only conversation flow. - * It accepts JSONL-formatted chat messages and renders them using - * UserMessage and AssistantMessage components with timeline styling. - * - * Features: - * - Auto-scroll to bottom when new messages arrive - * - Programmatic scroll control via ref - * - Light/dark/auto theme support - * - Empty state with customizable message - */ -const meta: Meta = { - title: 'Chat/ChatViewer', - component: ChatViewer, - parameters: { - layout: 'padded', - }, - tags: ['autodocs'], - argTypes: { - messages: { - control: 'object', - description: 'Array of chat messages in JSONL format', - }, - className: { - control: 'text', - description: 'Additional CSS class name', - }, - onFileClick: { action: 'fileClicked' }, - emptyMessage: { - control: 'text', - description: 'Message to show when there are no messages', - }, - autoScroll: { - control: 'boolean', - description: 'Whether to auto-scroll to bottom when new messages arrive', - }, - theme: { - control: 'select', - options: ['dark', 'light', 'auto'], - description: 'Theme variant for the viewer', - }, - showEmptyIcon: { - control: 'boolean', - description: 'Whether to show the icon in empty state', - }, - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export default meta; -type Story = StoryObj; - -// Helper function to create message data -const createMessage = ( - uuid: string, - type: 'user' | 'assistant', - text: string, - timestamp: string, - model?: string, -): ChatMessageData => ({ - uuid, - parentUuid: null, - sessionId: 'story-session', - timestamp, - type, - message: { - role: type === 'user' ? 'user' : 'model', - parts: [{ text }], - }, - model, -}); - -export const Default: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'How do I create a React component?', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - "To create a React component, you can use either a function or a class. Here's a simple example of a functional component:\n\n```tsx\nimport React from 'react';\n\nconst MyComponent: React.FC = () => {\n return
Hello, World!
;\n};\n\nexport default MyComponent;\n```\n\nThis creates a basic component that renders \"Hello, World!\". You can then use it in other components like ``.", - '2026-01-19T10:00:05.000Z', - 'coder-model', - ), - ], - }, -}; - -export const MultiTurn: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'What is TypeScript?', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - 'TypeScript is a strongly typed programming language that builds on JavaScript. It adds optional static typing and class-based object-oriented programming to the language.', - '2026-01-19T10:00:05.000Z', - 'coder-model', - ), - createMessage( - '3', - 'user', - 'How do I define an interface?', - '2026-01-19T10:00:30.000Z', - ), - createMessage( - '4', - 'assistant', - 'You can define an interface in TypeScript like this:\n\n```typescript\ninterface User {\n id: number;\n name: string;\n email?: string; // optional property\n}\n\nconst user: User = {\n id: 1,\n name: "John Doe"\n};\n```\n\nInterfaces help you define the shape of objects and enable better type checking.', - '2026-01-19T10:00:35.000Z', - 'coder-model', - ), - createMessage( - '5', - 'user', - 'Can interfaces extend other interfaces?', - '2026-01-19T10:01:00.000Z', - ), - createMessage( - '6', - 'assistant', - 'Yes! Interfaces can extend one or more interfaces. Here\'s an example:\n\n```typescript\ninterface Person {\n name: string;\n age: number;\n}\n\ninterface Employee extends Person {\n employeeId: string;\n department: string;\n}\n\n// Employee now has: name, age, employeeId, and department\nconst employee: Employee = {\n name: "Jane",\n age: 30,\n employeeId: "E001",\n department: "Engineering"\n};\n```\n\nYou can also extend multiple interfaces: `interface Manager extends Person, Employee { ... }`', - '2026-01-19T10:01:05.000Z', - 'coder-model', - ), - ], - }, -}; - -export const WithCodeBlocks: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'Show me how to implement a debounce function', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - `Here's a debounce function implementation in TypeScript: - -\`\`\`typescript -function debounce any>( - func: T, - wait: number -): (...args: Parameters) => void { - let timeoutId: ReturnType | null = null; - - return function (...args: Parameters) { - if (timeoutId) { - clearTimeout(timeoutId); - } - - timeoutId = setTimeout(() => { - func.apply(this, args); - timeoutId = null; - }, wait); - }; -} -\`\`\` - -**Usage example:** - -\`\`\`typescript -const handleSearch = debounce((query: string) => { - console.log('Searching for:', query); - // Make API call here -}, 300); - -// This will only execute once, 300ms after the last call -handleSearch('h'); -handleSearch('he'); -handleSearch('hel'); -handleSearch('hello'); -\`\`\` - -The debounce function delays the execution until there's a pause in calls for the specified wait time (300ms in this case).`, - '2026-01-19T10:00:10.000Z', - 'coder-model', - ), - ], - }, -}; - -export const LongConversation: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'I need to build a REST API. What framework should I use?', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - 'For building a REST API, there are several excellent options depending on your needs:\n\n1. **Express.js** - Minimal and flexible, great for small to medium projects\n2. **Fastify** - High performance, excellent for high-throughput APIs\n3. **NestJS** - Full-featured framework with TypeScript, great for enterprise applications\n4. **Hono** - Ultra-fast and lightweight, perfect for edge computing\n\nWhat language/runtime are you planning to use?', - '2026-01-19T10:00:05.000Z', - 'coder-model', - ), - createMessage( - '3', - 'user', - 'I want to use TypeScript with Node.js. The API will handle about 10k requests per second.', - '2026-01-19T10:00:30.000Z', - ), - createMessage( - '4', - 'assistant', - "For high throughput (10k req/s) with TypeScript, I recommend **Fastify**. It's designed for performance and has excellent TypeScript support out of the box.\n\nHere's a quick setup:\n\n```bash\nnpm init -y\nnpm install fastify\nnpm install -D typescript @types/node\n```\n\n```typescript\nimport Fastify from 'fastify';\n\nconst app = Fastify({ logger: true });\n\napp.get('/health', async () => {\n return { status: 'ok' };\n});\n\napp.listen({ port: 3000 }, (err) => {\n if (err) throw err;\n});\n```\n\nFastify benchmarks show it can handle 30k+ req/s on modest hardware.", - '2026-01-19T10:00:35.000Z', - 'coder-model', - ), - createMessage( - '5', - 'user', - 'How do I add request validation?', - '2026-01-19T10:01:00.000Z', - ), - createMessage( - '6', - 'assistant', - "Fastify has built-in JSON Schema validation. Here's how to add it:\n\n```typescript\nconst createUserSchema = {\n body: {\n type: 'object',\n required: ['email', 'name'],\n properties: {\n email: { type: 'string', format: 'email' },\n name: { type: 'string', minLength: 2 },\n age: { type: 'integer', minimum: 0 }\n }\n },\n response: {\n 201: {\n type: 'object',\n properties: {\n id: { type: 'string' },\n email: { type: 'string' },\n name: { type: 'string' }\n }\n }\n }\n};\n\napp.post('/users', { schema: createUserSchema }, async (req, reply) => {\n const { email, name, age } = req.body;\n // Create user...\n reply.code(201).send({ id: '123', email, name });\n});\n```\n\nInvalid requests automatically return 400 with detailed error messages.", - '2026-01-19T10:01:10.000Z', - 'coder-model', - ), - ], - }, -}; - -export const Empty: Story = { - args: { - messages: [], - emptyMessage: 'Start a conversation to see messages here', - }, -}; - -export const CustomEmptyMessage: Story = { - args: { - messages: [], - emptyMessage: 'No chat history available', - }, -}; - -export const SingleUserMessage: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'This is a single user message without any response yet.', - '2026-01-19T10:00:00.000Z', - ), - ], - }, -}; - -export const SingleAssistantMessage: Story = { - args: { - messages: [ - createMessage( - '1', - 'assistant', - 'This is a standalone assistant message, perhaps from a system prompt or welcome message.', - '2026-01-19T10:00:00.000Z', - 'coder-model', - ), - ], - }, -}; - -// Real conversation example - Chrome Extension Native Host debugging session -export const RealConversation: Story = { - args: { - messages: [ - { - uuid: '20fb9426-40ca-4e85-92a1-7add59082167', - parentUuid: null, - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T02:58:32.415Z', - type: 'user', - message: { - role: 'user', - content: `Message received: Object from: Object -service-worker.js:137 Attempting to connect to Native Host: com.qwen.cli.bridge -service-worker.js:202 Sending handshake... -service-worker.js:175 Native host disconnected -service-worker.js:177 Disconnect error: Object -service-worker.js:178 Disconnect error message: Native host has exited. -service-worker.js:207 Handshake timeout - no response from Native Host - -Cannot receive host info. Expected output: -service-worker.js:150 [Native Event] host_info Object -service-worker.js:552 [Qwen] host_info Object -service-worker.js:617 [Host] Info Object -service-worker.js:164 [Native Message] handshake_response Object -service-worker.js:231 Handshake successful: Object`, - }, - }, - { - uuid: '86f72c75-4626-4292-a26f-13db6f80d4be', - parentUuid: '20fb9426-40ca-4e85-92a1-7add59082167', - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T02:58:45.239Z', - type: 'assistant', - model: 'claude-opus-4-5-20251101', - message: { - role: 'assistant', - content: `I can see you're experiencing an issue with the Chrome extension's native host connection. The error shows that the native host is disconnecting with "Native host has exited" and there's a handshake timeout. However, the logs also show that sometimes the handshake is successful when the host_info is received properly. - -Let me help you troubleshoot this issue. First, I'll explore the codebase to understand how the native host communication works.`, - }, - }, - { - uuid: '6ffff579-8020-4ccb-9b79-7c1da76bbf82', - parentUuid: '86f72c75-4626-4292-a26f-13db6f80d4be', - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T02:58:52.409Z', - type: 'assistant', - model: 'claude-opus-4-5-20251101', - message: { - role: 'assistant', - content: `Let me create a todo list to track the troubleshooting steps: - -1. **Explore the codebase** to understand the native host implementation -2. **Check the native host configuration** and setup -3. **Analyze the service worker code** that handles native messaging -4. **Identify why the native host exits** unexpectedly -5. **Propose fixes** for the native host connection issue`, - }, - }, - { - uuid: '8fd3dc96-1d34-4dae-845a-4827f435b890', - parentUuid: '6ffff579-8020-4ccb-9b79-7c1da76bbf82', - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T02:58:57.366Z', - type: 'assistant', - model: 'claude-opus-4-5-20251101', - message: { - role: 'assistant', - content: `Let me start by exploring the codebase to understand the native host implementation. - -Looking at the project structure, I found the following relevant files: - -\`\`\` -packages/chrome-extension/ -├── src/ -│ ├── service-worker.ts # Main service worker -│ ├── native-messaging/ -│ │ ├── host.ts # Native host communication -│ │ └── protocol.ts # Message protocol -│ └── manifest.json # Extension manifest -└── native-host/ - ├── index.js # Native host entry - └── com.qwen.cli.bridge.json # Host manifest -\`\`\` - -The issue appears to be in the handshake timing. The native host is exiting before the handshake completes.`, - }, - }, - { - uuid: 'user-follow-up-1', - parentUuid: '8fd3dc96-1d34-4dae-845a-4827f435b890', - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T03:00:15.000Z', - type: 'user', - message: { - role: 'user', - content: 'How should I fix this issue?', - }, - }, - { - uuid: 'assistant-fix-1', - parentUuid: 'user-follow-up-1', - sessionId: 'f05dd1fc-2947-44e0-9f31-cbddce461844', - timestamp: '2026-01-09T03:00:45.000Z', - type: 'assistant', - model: 'claude-opus-4-5-20251101', - message: { - role: 'assistant', - content: `Based on my analysis, here's how to fix the native host connection issue: - -## Root Cause -The native host process is exiting prematurely because it's not keeping the stdin stream open for communication. - -## Solution - -Update the native host's \`index.js\`: - -\`\`\`javascript -// native-host/index.js -const { stdin, stdout } = process; - -// Keep the process alive by not closing stdin -stdin.resume(); - -// Handle incoming messages -stdin.on('data', (data) => { - try { - const length = data.readUInt32LE(0); - const message = JSON.parse(data.slice(4, 4 + length).toString()); - - if (message.type === 'handshake') { - sendMessage({ type: 'handshake_response', status: 'ok' }); - } - // Handle other messages... - } catch (err) { - console.error('Error parsing message:', err); - } -}); - -function sendMessage(msg) { - const json = JSON.stringify(msg); - const length = Buffer.alloc(4); - length.writeUInt32LE(json.length, 0); - stdout.write(Buffer.concat([length, Buffer.from(json)])); -} - -// Send host info on startup -sendMessage({ - type: 'host_info', - pid: process.pid, - node: process.execPath -}); -\`\`\` - -## Key Changes -1. Added \`stdin.resume()\` to prevent the process from exiting -2. Properly handle the native messaging protocol (4-byte length prefix) -3. Send \`host_info\` immediately on startup - -Would you like me to apply these changes to your codebase?`, - }, - }, - ], - }, - parameters: { - docs: { - description: { - story: - 'A real debugging conversation about Chrome extension native host connection issues. Demonstrates multi-turn conversation with code blocks and mixed languages (English and Chinese).', - }, - }, - }, -}; - -// Helper to create tool call data -const createToolCall = ( - id: string, - kind: string, - title: string, - status: 'pending' | 'in_progress' | 'completed' | 'failed', - content?: ToolCallData['content'], - locations?: ToolCallData['locations'], -): ToolCallData => ({ - toolCallId: id, - kind, - title, - status, - content, - locations, -}); - -// Conversation with tool calls - File reading and editing -export const WithToolCalls: Story = { - args: { - messages: [ - { - uuid: 'tc-1', - timestamp: '2026-01-15T14:00:00.000Z', - type: 'user', - message: { - role: 'user', - parts: [ - { - text: 'Please check the package.json file and add a lodash dependency', - }, - ], - }, - }, - { - uuid: 'tc-2', - timestamp: '2026-01-15T14:00:05.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'Sure, let me read the package.json file to check the current dependency configuration.', - }, - }, - { - uuid: 'tc-3', - timestamp: '2026-01-15T14:00:06.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'read-1', - 'read', - 'package.json', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `{ - "name": "my-project", - "version": "1.0.0", - "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - }, - "devDependencies": { - "typescript": "^5.0.0", - "vite": "^5.0.0" - } -}`, - }, - }, - ], - [{ path: 'package.json', line: 1 }], - ), - }, - { - uuid: 'tc-4', - timestamp: '2026-01-15T14:00:10.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'I see the project uses React 18 and TypeScript. Now let me add the lodash dependency.', - }, - }, - { - uuid: 'tc-5', - timestamp: '2026-01-15T14:00:11.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'edit-1', - 'edit', - 'package.json', - 'completed', - [ - { - type: 'diff', - path: 'package.json', - oldText: ` "dependencies": { - "react": "^18.2.0", - "react-dom": "^18.2.0" - },`, - newText: ` "dependencies": { - "lodash": "^4.17.21", - "react": "^18.2.0", - "react-dom": "^18.2.0" - },`, - }, - ], - [{ path: 'package.json', line: 4 }], - ), - }, - { - uuid: 'tc-6', - timestamp: '2026-01-15T14:00:15.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: `Successfully added lodash dependency to package.json. - -You need to run the following command to install the new dependency: - -\`\`\`bash -npm install -\`\`\` - -Or if you use pnpm: - -\`\`\`bash -pnpm install -\`\`\``, - }, - }, - ], - }, - parameters: { - docs: { - description: { - story: - 'Demonstrates a conversation with Read and Edit tool calls, showing file reading and modification.', - }, - }, - }, -}; - -// Conversation with Shell commands -export const WithShellCommands: Story = { - args: { - messages: [ - { - uuid: 'sh-1', - timestamp: '2026-01-15T15:00:00.000Z', - type: 'user', - message: { - role: 'user', - parts: [ - { text: 'Help me create a new React component folder structure' }, - ], - }, - }, - { - uuid: 'sh-2', - timestamp: '2026-01-15T15:00:05.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'Sure, let me create a standard React component folder structure.', - }, - }, - { - uuid: 'sh-3', - timestamp: '2026-01-15T15:00:06.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'shell-1', - 'bash', - 'mkdir -p src/components/Button', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: '', - }, - }, - ], - ), - }, - { - uuid: 'sh-4', - timestamp: '2026-01-15T15:00:08.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'write-1', - 'write', - 'src/components/Button/Button.tsx', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `import React from 'react'; -import './Button.css'; - -interface ButtonProps { - children: React.ReactNode; - onClick?: () => void; - variant?: 'primary' | 'secondary'; -} - -export const Button: React.FC = ({ - children, - onClick, - variant = 'primary', -}) => { - return ( - - ); -};`, - }, - }, - ], - [{ path: 'src/components/Button/Button.tsx' }], - ), - }, - { - uuid: 'sh-5', - timestamp: '2026-01-15T15:00:10.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'write-2', - 'write', - 'src/components/Button/Button.css', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `.button { - padding: 8px 16px; - border-radius: 4px; - border: none; - cursor: pointer; - font-size: 14px; - transition: background-color 0.2s; -} - -.button--primary { - background-color: #3b82f6; - color: white; -} - -.button--secondary { - background-color: #e5e7eb; - color: #1f2937; -}`, - }, - }, - ], - [{ path: 'src/components/Button/Button.css' }], - ), - }, - { - uuid: 'sh-6', - timestamp: '2026-01-15T15:00:12.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'write-3', - 'write', - 'src/components/Button/index.ts', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `export { Button } from './Button'; -export type { ButtonProps } from './Button';`, - }, - }, - ], - [{ path: 'src/components/Button/index.ts' }], - ), - }, - { - uuid: 'sh-7', - timestamp: '2026-01-15T15:00:15.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: `Created complete Button component structure: - -\`\`\` -src/components/Button/ -├── Button.tsx # Main component -├── Button.css # Styles -└── index.ts # Exports -\`\`\` - -You can use it like this: - -\`\`\`tsx -import { Button } from './components/Button'; - - -\`\`\``, - }, - }, - ], - }, - parameters: { - docs: { - description: { - story: - 'Shows a conversation with Shell and Write tool calls, creating a complete component structure.', - }, - }, - }, -}; - -// Conversation with Search and multiple file reads -export const WithSearchAndRead: Story = { - args: { - messages: [ - { - uuid: 'sr-1', - timestamp: '2026-01-15T16:00:00.000Z', - type: 'user', - message: { - role: 'user', - parts: [ - { text: 'Help me find all files that use useState in the project' }, - ], - }, - }, - { - uuid: 'sr-2', - timestamp: '2026-01-15T16:00:05.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'Sure, let me search for all files that use useState in the project.', - }, - }, - { - uuid: 'sr-3', - timestamp: '2026-01-15T16:00:06.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'search-1', - 'grep', - 'useState', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `src/App.tsx:3:import { useState } from 'react'; -src/components/Counter.tsx:1:import { useState } from 'react'; -src/components/Form.tsx:2:import { useState, useEffect } from 'react'; -src/hooks/useLocalStorage.ts:1:import { useState, useEffect } from 'react';`, - }, - }, - ], - [ - { path: 'src/App.tsx', line: 3 }, - { path: 'src/components/Counter.tsx', line: 1 }, - { path: 'src/components/Form.tsx', line: 2 }, - { path: 'src/hooks/useLocalStorage.ts', line: 1 }, - ], - ), - }, - { - uuid: 'sr-4', - timestamp: '2026-01-15T16:00:10.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: `Found **4 files** using \`useState\`: - -| File | Line | -|------|------| -| \`src/App.tsx\` | 3 | -| \`src/components/Counter.tsx\` | 1 | -| \`src/components/Form.tsx\` | 2 | -| \`src/hooks/useLocalStorage.ts\` | 1 | - -Would you like me to look at any specific file in detail?`, - }, - }, - { - uuid: 'sr-5', - timestamp: '2026-01-15T16:00:30.000Z', - type: 'user', - message: { - role: 'user', - parts: [{ text: 'Show me Counter.tsx' }], - }, - }, - { - uuid: 'sr-6', - timestamp: '2026-01-15T16:00:31.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'read-2', - 'read', - 'src/components/Counter.tsx', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `import { useState } from 'react'; - -export const Counter = () => { - const [count, setCount] = useState(0); - - return ( -
-

Count: {count}

- - -
- ); -};`, - }, - }, - ], - [{ path: 'src/components/Counter.tsx' }], - ), - }, - { - uuid: 'sr-7', - timestamp: '2026-01-15T16:00:35.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: `This is a simple counter component that uses \`useState\` to manage the count state. - -**Code Analysis:** -- Uses functional update \`c => c + 1\` to ensure correct state updates -- Simple component structure with only one state variable - -**Potential Improvements:** -1. Could add TypeScript type annotations -2. Could add min/max limits -3. Could add a step parameter - -Would you like me to optimize this component?`, - }, - }, - ], - }, - parameters: { - docs: { - description: { - story: - 'Demonstrates Search (grep) and Read tool calls in a code exploration conversation.', - }, - }, - }, -}; - -// Conversation with Todo/Plan updates -export const WithPlanUpdates: Story = { - args: { - messages: [ - { - uuid: 'plan-1', - timestamp: '2026-01-15T17:00:00.000Z', - type: 'user', - message: { - role: 'user', - parts: [ - { - text: 'Help me refactor the authentication module of this project', - }, - ], - }, - }, - { - uuid: 'plan-2', - timestamp: '2026-01-15T17:00:05.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'Sure, this is a complex task. Let me first create a plan to track progress.', - }, - }, - { - uuid: 'plan-3', - timestamp: '2026-01-15T17:00:06.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'todo-1', - 'todowrite', - 'Create refactoring plan', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: JSON.stringify([ - { - content: 'Analyze existing auth code structure', - status: 'in_progress', - }, - { - content: 'Design new auth architecture', - status: 'pending', - }, - { - content: 'Implement JWT token management', - status: 'pending', - }, - { content: 'Add refresh token mechanism', status: 'pending' }, - { content: 'Update related tests', status: 'pending' }, - ]), - }, - }, - ], - ), - }, - { - uuid: 'plan-4', - timestamp: '2026-01-15T17:00:10.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: - 'I have created the refactoring plan. Now let me analyze the existing auth code.', - }, - }, - { - uuid: 'plan-5', - timestamp: '2026-01-15T17:00:11.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'search-2', - 'glob', - '**/auth/**', - 'completed', - undefined, - [ - { path: 'src/auth/index.ts' }, - { path: 'src/auth/login.ts' }, - { path: 'src/auth/logout.ts' }, - { path: 'src/auth/middleware.ts' }, - { path: 'src/auth/types.ts' }, - ], - ), - }, - { - uuid: 'plan-6', - timestamp: '2026-01-15T17:00:15.000Z', - type: 'tool_call', - toolCall: createToolCall( - 'read-3', - 'read', - 'src/auth/index.ts', - 'completed', - [ - { - type: 'content', - content: { - type: 'text', - text: `// Legacy auth module -export { login } from './login'; -export { logout } from './logout'; -export { authMiddleware } from './middleware'; -export type { AuthUser, AuthToken } from './types';`, - }, - }, - ], - [{ path: 'src/auth/index.ts' }], - ), - }, - { - uuid: 'plan-7', - timestamp: '2026-01-15T17:00:20.000Z', - type: 'assistant', - message: { - role: 'assistant', - content: `Analysis complete! Existing auth module structure: - -\`\`\` -src/auth/ -├── index.ts # Export entry -├── login.ts # Login logic -├── logout.ts # Logout logic -├── middleware.ts # Auth middleware -└── types.ts # Type definitions -\`\`\` - -**Issues Found:** -1. No token refresh mechanism -2. Missing auth state persistence -3. Incomplete error handling - -**Suggested New Architecture:** -- Add \`tokenManager.ts\` for JWT management -- Add \`storage.ts\` for persistence handling -- Add \`errors.ts\` for unified error handling - -Should I continue with the implementation?`, - }, - }, - ], - }, - parameters: { - docs: { - description: { - story: - 'Shows a complex refactoring task with Todo/Plan updates, file search, and code analysis.', - }, - }, - }, -}; - -export const LightTheme: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'Show me how to use the light theme.', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - 'The ChatViewer supports light, dark, and auto themes. Set `theme="light"` for light mode styling.', - '2026-01-19T10:00:05.000Z', - 'coder-model', - ), - ], - theme: 'light', - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const AutoScrollDisabled: Story = { - args: { - messages: [ - createMessage( - '1', - 'user', - 'This story has auto-scroll disabled.', - '2026-01-19T10:00:00.000Z', - ), - createMessage( - '2', - 'assistant', - 'When `autoScroll={false}`, the viewer will not automatically scroll to the bottom when new messages arrive. This is useful when you want users to manually control the scroll position.', - '2026-01-19T10:00:05.000Z', - 'coder-model', - ), - ], - autoScroll: false, - }, -}; - -export const EmptyWithoutIcon: Story = { - args: { - messages: [], - emptyMessage: 'No messages yet', - showEmptyIcon: false, - }, -}; - -// Interactive story demonstrating ref functionality -const WithRefControlTemplate = () => { - const chatRef = useRef(null); - - const messages: ChatMessageData[] = Array.from({ length: 20 }, (_, i) => - createMessage( - String(i + 1), - i % 2 === 0 ? 'user' : 'assistant', - i % 2 === 0 - ? `Question ${Math.floor(i / 2) + 1}: How does feature ${Math.floor(i / 2) + 1} work?` - : `This is the answer to question ${Math.floor(i / 2) + 1}. The feature works by processing data through multiple stages and returning the result to the caller.`, - new Date(2026, 0, 19, 10, i).toISOString(), - i % 2 === 1 ? 'coder-model' : undefined, - ), - ); - - return ( -
-
- - -
-
- -
-
- ); -}; - -export const WithRefControl: Story = { - render: () => , - parameters: { - docs: { - description: { - story: - 'Demonstrates programmatic scroll control using the `ref` prop. The `ChatViewerHandle` provides `scrollToTop()`, `scrollToBottom()`, and `getScrollContainer()` methods.', - }, - }, - }, -}; - -// Comprehensive sample data for playground with all tool types -const PLAYGROUND_SAMPLE = `[ - { - "uuid": "1", - "timestamp": "2026-01-15T14:00:00.000Z", - "type": "user", - "message": { - "role": "user", - "parts": [{ "text": "Help me create a React component and add it to the project" }] - } - }, - { - "uuid": "2", - "timestamp": "2026-01-15T14:00:05.000Z", - "type": "assistant", - "message": { - "role": "assistant", - "content": "Sure, let me help you create a React component. First, let me search for the project structure." - } - }, - { - "uuid": "3", - "timestamp": "2026-01-15T14:00:06.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "search-1", - "kind": "grep", - "title": "Searching for component patterns", - "status": "completed", - "rawInput": "export.*Component", - "content": [{ - "type": "content", - "content": { - "type": "text", - "text": "src/components/Button.tsx:export const Button: FC = () => {\\nsrc/components/Card.tsx:export const Card: FC = () => {" - } - }], - "locations": [ - { "path": "src/components/Button.tsx", "line": 5 }, - { "path": "src/components/Card.tsx", "line": 8 } - ] - } - }, - { - "uuid": "4", - "timestamp": "2026-01-15T14:00:08.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "read-1", - "kind": "read", - "title": "src/components/Button.tsx", - "status": "completed", - "content": [{ - "type": "content", - "content": { - "type": "text", - "text": "import type { FC } from 'react';\\n\\nexport interface ButtonProps {\\n label: string;\\n onClick?: () => void;\\n}\\n\\nexport const Button: FC = ({ label, onClick }) => (\\n \\n);" - } - }], - "locations": [{ "path": "src/components/Button.tsx" }] - } - }, - { - "uuid": "5", - "timestamp": "2026-01-15T14:00:10.000Z", - "type": "assistant", - "message": { - "role": "assistant", - "content": "I found the project's component structure. Now let me create the new component file." - } - }, - { - "uuid": "6", - "timestamp": "2026-01-15T14:00:12.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "write-1", - "kind": "write", - "title": "Creating src/components/Modal.tsx", - "status": "completed", - "content": [{ - "type": "diff", - "path": "src/components/Modal.tsx", - "oldText": null, - "newText": "import type { FC, ReactNode } from 'react';\\nimport './Modal.css';\\n\\nexport interface ModalProps {\\n isOpen: boolean;\\n onClose: () => void;\\n title: string;\\n children: ReactNode;\\n}\\n\\nexport const Modal: FC = ({\\n isOpen,\\n onClose,\\n title,\\n children,\\n}) => {\\n if (!isOpen) return null;\\n\\n return (\\n
\\n
\\n
\\n

{title}

\\n \\n
\\n
\\n {children}\\n
\\n
\\n
\\n );\\n};" - }], - "locations": [{ "path": "src/components/Modal.tsx" }] - } - }, - { - "uuid": "7", - "timestamp": "2026-01-15T14:00:15.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "edit-1", - "kind": "edit", - "title": "Updating src/components/index.ts", - "status": "completed", - "content": [{ - "type": "diff", - "path": "src/components/index.ts", - "oldText": "export { Button } from './Button';\\nexport { Card } from './Card';", - "newText": "export { Button } from './Button';\\nexport { Card } from './Card';\\nexport { Modal } from './Modal';" - }], - "locations": [{ "path": "src/components/index.ts", "line": 3 }] - } - }, - { - "uuid": "8", - "timestamp": "2026-01-15T14:00:18.000Z", - "type": "assistant", - "message": { - "role": "assistant", - "content": "Component created. Let me run tests to make sure there are no issues." - } - }, - { - "uuid": "9", - "timestamp": "2026-01-15T14:00:20.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "bash-1", - "kind": "bash", - "title": "Running tests", - "status": "completed", - "rawInput": "npm run test -- --coverage", - "content": [{ - "type": "content", - "content": { - "type": "text", - "text": "PASS src/components/Modal.test.tsx\\n Modal Component\\n ✓ renders when isOpen is true (15ms)\\n ✓ does not render when isOpen is false (3ms)\\n ✓ calls onClose when close button clicked (8ms)\\n\\nTest Suites: 1 passed, 1 total\\nTests: 3 passed, 3 total\\nCoverage: 92.5%" - } - }] - } - }, - { - "uuid": "10", - "timestamp": "2026-01-15T14:00:25.000Z", - "type": "tool_call", - "toolCall": { - "toolCallId": "plan-1", - "kind": "todowrite", - "title": "Updating task progress", - "status": "completed", - "content": [{ - "type": "content", - "content": { - "type": "plan", - "entries": [ - { "content": "Search project structure", "status": "completed" }, - { "content": "Create Modal component", "status": "completed" }, - { "content": "Update exports", "status": "completed" }, - { "content": "Run tests", "status": "completed" }, - { "content": "Add documentation", "status": "pending" } - ] - } - }] - } - }, - { - "uuid": "11", - "timestamp": "2026-01-15T14:00:30.000Z", - "type": "assistant", - "message": { - "role": "assistant", - "content": "Modal component created successfully and passed all tests!\\n\\n**Created Files:**\\n- \`src/components/Modal.tsx\` - Main component file\\n- \`src/components/Modal.css\` - Styles file\\n\\n**Features:**\\n- Supports open/close state control\\n- Customizable title and content\\n- Close button triggers callback\\n\\nWould you like me to add documentation?" - } - } -]`; - -// Playground component for testing JSON input with auto-render -const PlaygroundTemplate = () => { - const [jsonInput, setJsonInput] = useState(PLAYGROUND_SAMPLE); - const [messages, setMessages] = useState([]); - const [error, setError] = useState(null); - const [autoRender, setAutoRender] = useState(true); - const debounceRef = useRef | null>(null); - - const parseAndRender = useCallback((input: string) => { - try { - const parsed = JSON.parse(input); - if (!Array.isArray(parsed)) { - throw new Error('JSON must be an array of messages'); - } - setMessages(parsed); - setError(null); - } catch (e) { - setError(e instanceof Error ? e.message : 'Invalid JSON'); - setMessages([]); - } - }, []); - - // Auto-render with debounce when JSON input changes - useEffect(() => { - if (!autoRender) return; - - if (debounceRef.current) { - clearTimeout(debounceRef.current); - } - - debounceRef.current = setTimeout(() => { - parseAndRender(jsonInput); - }, 300); - - return () => { - if (debounceRef.current) { - clearTimeout(debounceRef.current); - } - }; - }, [jsonInput, autoRender, parseAndRender]); - - // Parse on initial load - useEffect(() => { - parseAndRender(jsonInput); - }, [parseAndRender, jsonInput]); - - return ( -
- {/* Left Panel - JSON Input */} -
-
-

- JSON Input (Messages Array) -

-
- - {!autoRender && ( - - )} -
-
-