Skip to content

feat(web-shell): complete experimental intelligent UI with stateful interactions #14019

Open

Description

@LaZzyMan

Goal

Add a complete intelligent UI experience to Web Shell: the assistant can choose text, native interactive components, or an isolated HTML visualization according to the task. This is a user-visible feature, not a collection of hard-coded demos or a side-panel-only HTML preview. Mark it experimental, enable it by default, and provide a persistent setting to disable it.

Implementation may be organized internally, but delivery must include the entire functionality and acceptance criteria below. This issue is being filed before implementation so acceptance is not retrofitted to whatever happens to work.

Required behavior

  • Render interactive content within assistant responses while preserving ordinary Markdown and static fallbacks. Support native layouts, text, tables, charts, forms, inputs, selectors, sliders, buttons, lists and local state/calculations; retain an isolated HTML path for arbitrary visualizations and interactive mini-apps.
  • Support incremental generation and updates without resetting existing user state, blanking a valid view on an incomplete update, or executing incomplete code in the host application.
  • Local edits and calculations must not start model turns. An explicit user action may submit the current values into the same conversation through the existing session/authorization machinery. Never grant generated code unrestricted access to host APIs, files, credentials or tools.
  • Persist view state by the owning workspace, session, message and UI instance. Reload, leave/reopen and successive edits must have defined behavior; unrelated conversations must not share state. Handle save failures, timeouts, stale acknowledgements and capacity limits honestly.
  • Support successive requests that extend an existing interface while retaining compatible state. Export/copy must use current state, not initial sample values. Retain a useful static representation for history, disabled mode and unsupported clients.
  • Add experimental.intelligentUi, default true, exposed in Web Shell settings. Disabling must stop generated UI execution and future UI capability injection, not merely hide a visual element. Keep history readable and preserve saved data. CLI/headless clients must not inadvertently receive an unusable Web Shell-only experience.
  • Match Web Shell's themes, shared primitives, portal roots and CSS isolation. Support keyboard/IME interactions, accessible labels and narrow viewports.

Reference behavior and evidence boundaries

We inspected four Codex-created reference cases: a dinner bill splitter, persistent meeting checklist, team-activity form and subscription comparator. The observed Codex path used its visualize skill, generated HTML and a host bridge; it must not be confused with ChatGPT's separate DIL/native-component pipeline.

The reference cases exposed useful negative examples: inline sandbox timeouts despite working standalone previews, a form bridge tested only with stubs, and a supposed conversation-isolation test that only changed a URL query parameter. These failures must be retained as acceptance lessons, not copied into this feature or counted as passing behavior. No private transcripts or credentials are attached to this issue.

Acceptance criteria established before implementation

End-to-end reference cases

  1. Bill splitter: inputs update amounts, chart and per-person allocation immediately. Defaults produce CNY 924 total and CNY 154 each for six people. With seven people, CNY 61 discount and 15% service fee, total is CNY 964.85: the first four pay CNY 137.84 and the remaining three CNY 137.83. Reject invalid inputs, conserve cents, copy current values, and reset all views consistently.
  2. Checklist: add/edit/check/delete/filter and repeated undo work, including empty states. Wait for a real save acknowledgement, reload and reopen the actual conversation, and verify progress and undo data. Use two real sessions/workspaces to prove isolation, not two URLs or a fake storage API.
  3. Activity form: edits do not send turns. One explicit submission sends exactly one new turn with every current field. Verify the actual backend receives the changed values and the next answer uses them. Missing/denied/failed bridges must not report success. No real reservation, payment or notification is performed.
  4. Comparator: correctly distinguish monthly equivalents from actual annual payments. After choosing 13 people, annual billing and the team plan, add support at CNY 12/person/month without the annual discount and preserve choices. Add current-state CSV export and reset through another real follow-up. Annual totals with support are CNY 6739.20 / 10483.20 / 14227.20 for basic/pro/team. Verify the export and version/state relationship, not only a screenshot.

Cross-cutting gates

  • Exercise the real Web Shell message pipeline and embedded host, not only standalone previews, static fixtures, jsdom, or mocked host callbacks.
  • Test enabled-by-default, persistent opt-out, re-enable, unsupported clients, old history, interrupted streaming, malformed payloads, cancellation, disconnect/reconnect and last-valid-view recovery.
  • Test workspace/session ownership on all read/write/action paths; rejection must never fall back to a different runtime/session. Verify replay protection and explicit-user-action requirements for privileged bridges.
  • Test state-write rejection, timeout, capacity, racing updates and stale acknowledgements. Errors must be visible and recoverable without pretending data was saved.
  • Test light/dark themes, narrow screens, keyboard and IME input, stable chart rendering, copying and downloads. Check calculation/semantic correctness separately from visual appearance.
  • Build, typecheck, targeted unit/integration tests, actual browser end-to-end tests, and independent review must pass before opening the completed implementation PR.

Model validation

Use the authorized GPT-6 deployment as the implementation/E2E target. Separately provide a Qwen3.8-Max generation/compatibility comparison using the same acceptance tasks; this comparison does not gate or reduce the full feature scope. Endpoint names and response formats must be verified before use, and no credentials may enter source, logs or the PR.

Delivery

A complete implementation PR linked to this issue, including design/acceptance documentation, tests, reproducible verification results and the separate model comparison. Do not close this issue on an intermediate renderer, demo, or partial implementation.

Activity

  1. qwen-code-review-bot commented on Oct 13, 2026

    @qwen-code-review-bot
    Collaborator

    ✅ Qwen Triage finished — view run. See the stage comments in this thread for the result.

    ✅ Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

  2. qwen-code-review-bot commented on Oct 13, 2026

    @qwen-code-review-bot
    Collaborator

    Triage

    Problem — real. Assistant output in Web Shell is Markdown plus an isolated HTML artifact today; there is no native interactive surface that keeps local state across turns. The four reference cases are concrete, and fixing acceptance before implementation rather than after is the right instinct.

    Direction — aligned with where Web Shell is heading, but the spec never names the three surfaces it overlaps, and that choice shapes everything downstream:

    • MCP Apps is already in tree. packages/web-shell/client/components/messages/McpApp.tsx hosts generated HTML in a sandboxed loopback-origin iframe through @modelcontextprotocol/ext-apps AppBridge / PostMessageTransport, with CSP, permissions, toolResult and fallbackText. Tracked by feat(mcp): support MCP 2026-07-28 and MCP Apps in daemon WebShell #8968, and feat(web-shell): review MCP App messages before submission (PR3a) #13958 (+2697/−32, open) is building "review MCP App messages before submission" — which is precisely your requirement that local edits never start a turn and only one explicit action submits current values.
    • The isolated HTML path already exists: ArtifactPanel.tsx, artifactUtils.ts, SavedWebPreview.tsx, preview/web-preview.ts (sandbox="allow-scripts", CSP default-src 'none', frame-src 'none'), e2e in client/e2e/web-shell.live-preview.spec.ts, gated by experimental.artifact (default true).
    • The ownership keying you want to persist by is already shaped this way: client/App.tsx builds preview identity as web-preview:{sourceSessionId}:{turnId}:{artifactId}.

    So the open question: is intelligent UI a fourth surface, or the model-driven producer of the existing MCP App / artifact surface? Reusing the McpApp bridge would absorb most of the transport, CSP, permissions, submission-gate and static-fallback work the spec lists as new. Worth an explicit answer here before code.

    Approach — two suggestions:

    1. "The entire functionality in one PR" will not survive review. Requirements 1–7 are separately shippable: capability injection plus the setting, then the native renderer with local state, then persistence and ownership, then explicit submit, then incremental streaming updates, then export and static fallback, then theme/a11y/IME. Staged delivery with its own slice of the acceptance list per stage reaches the same end state and keeps every step revertable.
    2. The numeric acceptance values (CNY 924/154, 964.85, 137.84/137.83, 6739.20/10483.20/14227.20) are properties of four specific demo fixtures. Stating the invariants instead — cents conserved, the allocation and rounding rule, monthly-equivalent versus actual annual payment, support added without the annual discount — with one numeric case per invariant, is what actually stops acceptance being retrofitted to whatever the demos happen to produce.

    Two smaller questions. Making the authorized GPT-6 deployment the implementation/E2E target with Qwen3.8-Max as a non-gating comparison inverts the priority for this repo — should Qwen3.8-Max be the gating target and the other model compatibility evidence? And every experimental.* entry in settingsSchema.ts is requiresRestart: true, so an opt-out that must stop generated UI execution in a live session needs a decision on restart versus immediate effect.

    experimental.intelligentUi defaulting to true has precedent in experimental.artifact, and the two constraints that matter most are already in the spec: disabling stops capability injection instead of hiding an element, and CLI/headless clients do not inherit a Web Shell-only experience.

    Verdict

    Accept for exploration, with two preconditions that are cheap to decide now and expensive to decide after a full implementation lands:

    1. State the relationship to MCP Apps explicitly — reuse the existing host bridge as the execution and submission channel, or justify a fourth parallel surface. Requirement 3 (no implicit turns, one explicit submission, denied or failed bridges never report success) is already the subject of feat(web-shell): review MCP App messages before submission (PR3a) #13958; building a second copy of that gate is the main way this spec doubles its own work.
    2. Restate delivery as stages, each independently mergeable and each carrying its own slice of the acceptance list. The issue can stay open until the last stage lands — that satisfies "do not close on a partial implementation" without requiring one unreviewable PR.

    Not a smaller-alternative suggestion and not a decline: the capability gap is genuine, the negative examples carried over from the reference cases (sandbox timeouts that only appear inline, a bridge tested against stubs, an isolation test that only changed a URL query parameter) are exactly the failure modes worth writing down, and the security constraints in the spec match how this repo already treats workspace-scoped routes and model-supplied content.

    中文说明

    问题:成立。目前 Web Shell 的助手输出只有 Markdown 加一个隔离的 HTML artifact,没有能跨轮次保留本地状态的原生交互界面。四个参考案例很具体,"先定验收再实现"的思路也是对的。

    方向:与 Web Shell 的演进方向一致,但规格没有点名它重叠的三个既有能力,而这正是决定后续一切的选择:

    • MCP Apps 已经在仓库里:packages/web-shell/client/components/messages/McpApp.tsx 通过 @modelcontextprotocol/ext-apps 的 AppBridge / PostMessageTransport,把生成的 HTML 放在 loopback 独立源的 sandbox iframe 中运行,带 CSP、permissions、toolResult 和 fallbackText。对应 feat(mcp): support MCP 2026-07-28 and MCP Apps in daemon WebShell #8968,而 feat(web-shell): review MCP App messages before submission (PR3a) #13958(+2697/−32,开放中)正在做"提交前先审阅 MCP App 消息"——这恰好就是"本地编辑不触发模型轮次、只有显式操作才提交当前值"这条要求。
    • 隔离 HTML 通道已经存在:ArtifactPanel.tsx、artifactUtils.ts、SavedWebPreview.tsx、preview/web-preview.ts(sandbox="allow-scripts"、CSP default-src 'none'、frame-src 'none'),e2e 见 client/e2e/web-shell.live-preview.spec.ts,由 experimental.artifact(默认 true)控制。
    • 你要求按 workspace/session/message/UI 实例持久化,现有代码已经是同样的键形状:client/App.tsx 用 web-preview:{sourceSessionId}:{turnId}:{artifactId} 构造预览身份。

    所以待决问题是:intelligent UI 是第四个界面,还是既有 MCP App / artifact 界面的"模型驱动生产者"?复用 McpApp bridge 可以吸收规格里列为新增的大部分传输、CSP、权限、提交闸门和静态降级工作。建议在写代码前先在 issue 里给出明确答案。

    方案:两点建议:

    1. "一个 PR 交付全部功能"过不了 review。需求 1–7 可以分别交付:能力注入加设置项 → 带本地状态的原生渲染器 → 持久化与归属 → 显式提交 → 流式增量更新 → 导出与静态降级 → 主题/无障碍/输入法。分阶段交付、每阶段带走验收清单的一部分,最终形态不变,但每一步都可回滚。
    2. 数字化的验收值(CNY 924/154、964.85、137.84/137.83、6739.20/10483.20/14227.20)是四个 demo 固定数据的属性。改成陈述不变量——分币守恒、分摊与取整规则、月度等值与实际年付的区别、附加 support 时不套用年付折扣——每个不变量保留一个数字用例,才真正能防止"验收被实现结果倒推"。

    另外两个小问题:把授权的 GPT-6 部署作为实现/E2E 目标、而 Qwen3.8-Max 只作不 gating 的对比,对本仓库来说优先级是反的——gating 目标是否应该是 Qwen3.8-Max,另一个模型作为兼容性证据?以及 settingsSchema.ts 中 experimental.* 全部是 requiresRestart: true,那么"必须立即停止已生成 UI 执行"的退出开关,需要在重启生效与立即生效之间做出决定。

    experimental.intelligentUi 默认 true 有 experimental.artifact 的先例;最关键的两条约束规格里已经写了:关闭时停止能力注入而不只是隐藏元素,以及 CLI/headless 客户端不会继承只在 Web Shell 可用的体验。

    结论:接受进入探索,但有两个前置条件——它们现在决定成本很低,等完整实现落地后再决定就很贵:

    1. 明确与 MCP Apps 的关系:要么复用既有 host bridge 作为执行与提交通道,要么论证为什么需要第四条并行界面。需求 3(不隐式触发轮次、只有一次显式提交、bridge 缺失/被拒/失败都不得报告成功)正是 feat(web-shell): review MCP App messages before submission (PR3a) #13958 在做的事;再建一套同样的闸门是本规格把自身工作量翻倍的主要途径。
    2. 把交付改写为阶段,每阶段可独立合入并带走验收清单的一部分。issue 可以一直开到最后一个阶段落地——这样既满足"不因部分实现而关闭",又不需要单个无法 review 的 PR。

    不建议缩小范围,也不建议拒绝:能力缺口是真实的;从参考案例保留下来的反面例子(只在内联时超时的 sandbox、只用 stub 测过的 bridge、只改了 URL query 参数的隔离测试)正是值得写下来的失效模式;规格中的安全约束也与本仓库对 workspace 作用域路由和模型提供内容的既有处理方式一致。

    --- Qwen Code

  3. doudouOUC commented on Oct 13, 2026

    @doudouOUC
    Collaborator

    I independently verified every existing surface this issue and the triage stage-1 comment reference against the current main HEAD (6373fc4e5e1ba66e4776075132d7c9fdc8df5c29). All three overlapping capabilities are confirmed in-tree, and I want to add a few concrete deltas that sharpen the open questions and one factual correction to the triage comment.

    Verified existing surfaces (byte-confirmed)

    Surface Location Confirmed
    MCP App bridge packages/web-shell/client/components/messages/McpApp.tsx (391L) AppBridge / PostMessageTransport from @modelcontextprotocol/ext-apps/app-bridge; CSP / permissions / toolResult / fallbackText all present
    Isolated HTML artifact packages/web-shell/client/components/artifacts/ArtifactPanel.tsx (4174L) + artifactUtils.ts (573L) + components/preview/web-preview.ts (74L) + components/preview/SavedWebPreview.tsx (130L) present; experimental.artifact default true, requiresRestart: true
    Preview identity key App.tsx:7328 web-preview:${request.sourceSessionId ?? connection.sessionId}:${request.turnId}:${request.artifactId} — exact keying shape confirmed (with one nuance, below)
    E2E client/e2e/web-shell.live-preview.spec.ts (397L) present
    PR #13958 (submission gate) open, not merged, mergeable_state: blocked, +2697/−32 confirmed — this is the WIP for requirement 3 (no implicit turns, one explicit submit)
    experimental.intelligentUi search returns 0 hits net-new — consistent with the issue; not a rename of an existing flag

    Delta 1 — factual correction on requiresRestart

    The triage stage-1 comment states "every experimental.* entry in settingsSchema.ts is requiresRestart: true." That is not accurate. Within the experimental block (settingsSchema.ts:4269+) the split is 12 true / 11 false:

    • requiresRestart: false: liveVoice.enabled, liveVoice.model, liveVoice.endpoint, liveVoice.voice, liveVoice.shortcut, sessionWorkflow, agentChainLimit, agentTokenBudget, emitToolUseSummaries, and several nested publisher/oss sub-keys.
    • requiresRestart: true: mods, cron, todoStopGuard, sessionWriterLease, agentTeam, agentCollaboration, artifact, autoOpen, publisher.*, oss.

    So there is precedent for requiresRestart: false inside experimental.* (e.g. liveVoice.enabled can flip live). The underlying point the triage made — that a live opt-out must stop already-running generated UI execution, not just hide an element, which is a real runtime concern (tearing down existing iframe bridges mid-session) — still stands. But it is a runtime-semantics question, not a schema-convention question; requiresRestart: false is available if the bridge-cleanup path is handled explicitly. This removes one of the two "smaller questions" the triage flagged as needing a decision before implementation.

    Delta 2 — the dataMode auto-upgrade is the mechanism behind the triaged negative example

    The issue calls out a reference failure: "inline sandbox timeouts despite working standalone previews." That failure mode is already partially addressed in McpApp.tsx, and the shape of the fix matters for any fourth-surface design:

    • resolveMcpAppSandboxUrl (McpApp.tsx:72-101) takes dataMode = false but then forces it true at :84 when host.protocol === 'https:' OR the host/sandbox hostname is non-loopback. The dedicated loopback HTTP sandbox listener is only used when everything is localhost; HTTPS or any non-loopback deployment silently switches to mode=data (a different transport — content posted via the bridge rather than a dedicated HTTP origin).
    • The 10-second readyTimeout (:276-281) fires when the sandbox isn't ready in time; for the non-data path it falls back to setDataSandbox(true) (retries as data mode), and for the already-data path it calls failInitialization() (hard fail).

    So the "works standalone, times out inline" asymmetry the issue wants retained as an acceptance lesson is already encoded as a fallback path here. A native-component renderer that bypasses the iframe entirely (the "fourth surface" option) would sidestep this, but only if it also replaces the loopback-vs-data transport bifurcation — otherwise the same timeout class reappears in a different wrapper.

    Delta 3 — the McpApp height cap (640px)

    onsizechange (McpApp.tsx:304-310) clamps Math.min(640, Math.max(120, Math.ceil(requestedHeight))). The bill-splitter acceptance case (chart + per-person allocation) and the comparator (table + monthly/annual distinction) plausibly need more vertical room than 640px. If intelligent UI reuses the McpApp bridge, this cap is inherited; if it is a native-component surface, the cap is not inherited but the layout contract needs to be restated. Worth pinning down which path the design takes before sizing the acceptance fixtures.

    Delta 4 — requirement 4 (local-state persistence) is genuinely NEW, not reuse

    The triage suggested reusing the existing host bridge would absorb "most of the transport, CSP, permissions, submission-gate and static-fallback work." That is correct for transport/submission, but requirement 4 ("persist view state by workspace, session, message and UI instance … local edits and calculations must not start model turns") goes beyond what the existing persistence path does:

    • SavedWebPreview.tsx:41-45 fetches content via client.readSessionArtifactContent(sessionId, artifact.id, …) — it rehydrates the model-published HTML snapshot, nothing more. There is no store for user-local interactive state (form values, slider positions, checklist items the user typed).
    • The key SavedWebPreview uses (:34) is ${sessionId}:${artifact.id}:${attempt} — session+artifact, keyed to the model's artifact id, not to a user-edit-state instance.

    So requirement 4 needs a net-new workspace-scoped local-state store (with honest save-failure / timeout / capacity handling — also in the acceptance list). The bridge handles submission; it does not handle local-state durability. Conflating the two would under-scope the storage work.

    Delta 5 — minor isolation nuance on the preview key

    The identity key at App.tsx:7328 is web-preview:${request.sourceSessionId ?? connection.sessionId}:${turnId}:${artifactId}. The ?? connection.sessionId fallback means a preview tab with an unexpectedly-absent sourceSessionId falls back to the current connection's session — relevant to the "unrelated conversations must not share state" acceptance gate. If the design persists local state under this key, the fallback path needs an explicit decision (reject vs current-session-bind) so a stale tab can't silently bind to a different session.

    Two questions that need an answer before implementation

    These are cheap to decide now and expensive to decide after a full PR, and neither is locatable from source alone:

    1. Fourth surface or producer of the existing McpApp/artifact surface? The triage already asked this; I want to attach the concrete constraints that shape the answer. If fourth-surface (native components, no iframe): you inherit neither the 640px cap nor the dataMode/loopback transport bifurcation (Delta 2/3), but you must restate layout + CSP + the submission gate from scratch — and feat(web-shell): review MCP App messages before submission (PR3a) #13958's submission-gate work would need a second copy. If producer (model-driven McpApp/artifact with local-state extension): you inherit the bridge/CSP/submission-gate and feat(web-shell): review MCP App messages before submission (PR3a) #13958's work, but also the 640px cap and the dataMode timeout surface, and requirement 4's local-state store is still net-new (Delta 4). Which set of inherited constraints does the design accept?

    2. Local-state persistence surface. Requirement 4's store has no existing primitive to lift (SavedWebPreview persists model HTML only). Is the intended persistence backend a new workspace-scoped daemon endpoint (mirroring readSessionArtifactContent's shape, but for local state keyed by workspaceId:sessionId:turnId:uiInstanceId), browser-side storage (IndexedDB, with the cross-session-isolation risk that brings), or an extension of the existing artifact content record? The acceptance list tests "two real sessions/workspaces to prove isolation, not two URLs or a fake storage API" — that gate is specifically ruling out the URL-query-parameter isolation the reference case relied on, so the backend choice needs to be named explicitly.

    I concur with the triage's verdict (accept for exploration) and with its two preconditions (state the MCP-Apps relationship; restate delivery as stages). The deltas above are meant to make those two preconditions concrete enough to decide in this thread rather than after a first implementation pass.

    中文说明

    独立对照当前 main HEAD(6373fc4e5e1ba66e4776075132d7c9fdc8df5c29)逐项验证了 issue 与 triage stage-1 引用的三处既有能力,全部确认在树中。补充几个使开放问题更具体的 delta,以及一处对 triage 评论的事实更正。

    更正:triage 称 experimental.* 全部 requiresRestart: true,不准确。experimental 块(settingsSchema.ts:4269+)实际为 12 true / 11 false——liveVoice.enabled、agentChainLimit、emitToolUseSummaries 等为 false,即 requiresRestart: false 在 experimental 内有先例。triage 的核心观点(关闭时必须拆除已运行的 iframe bridge,是运行时语义问题而非 schema 约定)仍成立,但"无先例"的论证依据需要修正。

    Delta 2:McpApp.tsx:84 的 dataMode 自动升级(HTTPS 或非 loopback 时强制 data mode)+ 10s readyTimeout 降级——正是 issue 引用的"独立预览能跑、内联超时"反面例子的既有缓解路径。第四界面设计若绕过 iframe,需同时替代 loopback-vs-data 传输分叉,否则同类超时换皮重现。

    Delta 3:McpApp 高度上限 640px(:309),账单分摊图表/对比器表格的验收案例可能需要更高——复用 bridge 即继承此上限。

    Delta 4:需求 4 的本地状态持久化是净新增——SavedWebPreview 只通过 readSessionArtifactContent 取回模型发布的 HTML 快照,不保存用户本地交互状态(表单值/滑块/清单项)。需要新的 workspace 作用域本地状态存储,bridge 只管提交不管本地状态耐久性。

    Delta 5:预览身份键 App.tsx:7328 的 sourceSessionId ?? connection.sessionId 回退——sourceSessionId 缺失时回退到当前连接会话,与"不相关会话不得共享状态"验收门相关,需显式决策。

    两个需要在实现前回答的问题:(1)第四界面 vs 既有 McpApp/artifact 界面的生产者?各自继承的约束集不同(640px 上限、dataMode 超时面 vs 从头重述 CSP/提交闸门/#13958 第二份拷贝);(2)本地状态持久化后端——新 daemon 端点 / 浏览器侧 IndexedDB / 扩展 artifact 内容记录?验收清单明确排除了 URL-query 参数隔离,后端选择需显式命名。

    同意 triage 结论(接受探索)与两个前置条件(明确与 MCP Apps 关系、分阶段交付)。上述 delta 意在让这两个前置条件足够具体,可在本帖决定而非等首次实现后再定。

    --- AI00003514 (小企)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions