Files
qwen-code/integrations/generated-ui
chencheng (云谦)andShaojin Wen 29fa7bbe06 chore(types): compile cleanly under the TypeScript 7 native compiler (#16463)
* fix(types): compile cleanly under the TypeScript 7 native compiler

tsgo (@typescript/native-preview) reported 48 errors across the
typecheck projects while tsc 5.8 reported none. This removes all of
them without changing what tsc emits, so a tsgo typecheck lane or a
later compiler switch can start from a clean tree.

- Drop `baseUrl` from five tsconfigs. TypeScript 7 removed it, and
  every `paths` target here is already relative to its tsconfig.
- Set `rootDir` where TypeScript 7 no longer infers it (web-shell
  declarations, sdk-typescript), and list node types explicitly for
  cua-driver now that @types are no longer included by default.
- Annotate Buffers built by Node's Buffer factories as
  `Buffer<ArrayBuffer>` (or widen to `Buffer` where a variable later
  receives stream chunks); the ES2024 lib no longer treats
  ArrayBufferLike as an ArrayBuffer, so these failed at fetch, Blob,
  and Response call sites. The two call sites that take caller- or
  interface-supplied bytes assert `BodyInit`, matching the existing
  XMLHttpRequest upload path.
- Remove three redundant `?? []` / `?? undefined` operands that the
  newer checker reports as unreachable.
- Declare `*.css` modules for the two integrations that import
  stylesheets, now that side-effect imports are checked.

* fix(types): include web-shell client declarations in the build config

The config's include listed only client/index.tsx, so client/vite-env.d.ts
and client/local-files/file-system-access.d.ts never entered the program.
Removing baseUrl unmasked 227 tsgo errors (and 222 under tsc 5.8) for the
CSS module and File System Access declarations they provide.

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-11-07 08:23:16 +00:00
..
2026-11-06 18:03:10 +00:00

Generated UI

Generated UI is bundled with Qwen Code and on by default through the experimental.generatedUi setting. It creates complete, locally interactive views such as parameter simulations and weighted option comparisons. The model supplies HTML, CSS and JavaScript through generated_ui_show; the existing MCP Apps host displays the completed view. Each call creates an independent immutable card.

This is a restricted JavaScript environment with a small UI API. Layout is generated freely within the supported HTML/CSS subset; JavaScript executes in QuickJS in a dedicated Worker. Generated scripts never execute in the browser document. There is no browser DOM API, networking, imports, timers, filesystem access, MCP tool access, or message submission in generated code.

Enable the bundled feature

Generated UI is on by default in clients that render MCP Apps, such as Web Shell. To turn it off, disable Generated UI in Experimental settings, or set experimental.generatedUi to false in your Qwen Code settings. Recreate the owning CLI/daemon session to apply the change. Refreshing the browser or reattaching to an existing session does not apply a setting that requires restart. The same restart requirement applies when disabling the feature; after it takes effect, built-in historical cards display their text summary without starting the interactive runtime.

The built-in provider is offered only to sessions whose client renders MCP Apps: trusted local daemon sessions viewed in Web Shell or Desktop. The daemon declares this when it starts a session; interactive CLI/TUI, qwen -p and other headless runs, editor ACP clients and channels, including channels the daemon runs, never receive the tool or skill, so the model cannot point their users at a card they cannot see. It is also unavailable in Safe/Bare, managed, SSH or other execution environments, shell execution sandboxes, read-only Agent Hosts, and untrusted or unresolved workspaces. Existing MCP and skill restrictions still apply. A card opened in a client that cannot render it shows the readable summary.

The built-in provider uses ui://qwen-code-generated-ui/view-v1.html. A manually installed generated-ui extension keeps the legacy URI ui://generated-ui/view-v1.html and its existing behavior. An active manually configured provider with the same server name prevents built-in activation with a diagnostic; its configuration is preserved.

The product package includes the server, fixed App HTML, writer skill and third-party licenses under generated-ui/. Worker code and WASM are embedded build data, so using the installed feature does not download a runtime or library.

Build and configure the optional extension

Requires Node.js 22 or newer. From the repository root:

corepack pnpm install --frozen-lockfile
corepack pnpm --filter @qwen-code/generated-ui run build

Use Qwen Code's extension install/link command with this directory; check the installed CLI's extensions --help. The bundled stdio server needs no credentials, workspace cache or configuration environment variables. For another MCP host, configure node /absolute/extension/dist/main.js.

The package is private and is not automatically published. The packed extension includes the server, the fixed App HTML and its writer skill. Worker code and WASM are bundled as fixed compressed build data; the browser does not fetch a runtime or library from a CDN. The complete App HTML must fit the host's default 1 MiB resource limit.

Document and UI API

Call generated_ui_show with exactly these five string fields:

Field Meaning Maximum characters
title A short visible title 120
summary A useful standalone answer, initial result and assumptions 2,000
html HTML body content with stable element IDs 12,000
css Local styles 6,000
javascript Synchronous JavaScript using the API below 12,000

The complete serialized tool result must also fit 24,000 characters and 24,000 UTF-8 bytes, including JSON escaping, metadata and summaries. These are combined limits, not allowances to fill every field independently. Oversized documents are rejected without truncating their source.

HTML is sanitized before rendering and again for each HTML update. Use headings, paragraphs, lists, tables, labels, buttons, supported inputs and simple SVG. Every supplied ID must be unique, start with an ASCII letter, and contain only letters, digits, underscores or hyphens, up to 64 characters. Each input, select and textarea requires an ID; other elements need IDs when used by the UI API. Supported controls are text/number/range/checkbox/radio inputs, single-value selects and textareas; a multi-select is rejected because an input snapshot carries one value per control. Keep scripts in javascript and styles in css. URLs, external resources, links, forms, embeds, event-handler attributes and active content are excluded. CSS cannot load external resources. The renderer bounds the document to 512 elements and 64 controls. Content renders in a frame that does not execute scripts.

API Behavior
ui.on(id, type, handler) Register an input, change or click handler for an existing element ID. The callback receives { id, type, value, checked }, not a browser event.
ui.input(id) Read an input snapshot { value, checked }. value is a string and checked is a boolean. Convert numbers explicitly.
ui.text(id, text) Replace an element's text with a string. Prefer this for values and user input.
ui.html(id, html) Replace children with a sanitized body-level fragment, subject to the same document limits.
ui.value(id, value) Queue an input value and expose the requested string to subsequent ui.input calls in the same round.

ui.value updates the same-round snapshot even for an input created earlier in that round with ui.html. These reads reflect the requested string; browser normalization (such as number validation, range clamping or select matching) and HTML-created default values appear in the next dispatched input snapshot. Validate values against the control's constraints before calculating derived results.

There is no document, window, fetch, require, module loader, timer or browser event object. Do not call querySelector, create script tags, use inline onclick, import a charting library, or assume ui.html can enable additional capabilities. Use local JavaScript calculations and render the resulting strings. Register handlers at initialization and calculate the initial display once.

ui.html does not parse in the target element's context. Standalone table-row/cell/caption fragments lose their tags in body-level parsing. Update a table by putting a complete <table> into a container, or give cells IDs and use ui.text. Lists and option fragments remain supported. Use ui.value to change a control's current value; text/HTML changes that indirectly alter control or option values must satisfy the same input limits. A rejected patch batch leaves the displayed document unchanged and ends the activation with an error.

Handlers run in order and read the currently displayed input values when dispatched. Consecutive input events on the same control may be combined. At most 16 events wait for the Worker; when the queue is full, additional events that cannot be combined are rejected with a visible notice without ending the activation. The notice remains for this activation, including after later successful updates. Rejected actions are not automatically retried. Do not rely on every click or intermediate input being delivered. If an earlier round would overwrite a newer queued user edit, its entire patch batch is discarded, including derived text/HTML. This also covers indirect control changes from HTML/text updates, such as rebuilding dropdown options. The queued handler runs against current inputs; a view with only a Calculate button requires that button to recalculate.

The fixed App distinguishes updating, parameters awaiting an update, applied updates, skipped older updates, and queue rejection. An older batch that does not overwrite newer inputs may still change the display, but is not reported as applied for the current parameter version. Editing parameters without a matching input/change handler leaves them awaiting an update until the view's update action completes. An unhandled notification with unchanged values preserves the previous applied version. A registered handler that produces no patches is a valid no-op. The frame becomes visible after validated initialization even if more events remain queued or its initial update was superseded.

Discarding patches does not roll back mutations to JavaScript variables or automatically rerender the view. A guest counter can advance while its displayed value stays unchanged until a later render. Keep the state used by displayed results in controls and calculate all dependent outputs from ui.input() on each render. Do not use closure counters, toggles or history lists as the source of displayed state across rounds: this API provides no transactional rollback or exactly-once event delivery.

Use synchronous calculations. Uncaught synchronous exceptions and rejections of promises returned by an event handler end the activation. Ordinary promise errors can be handled with .catch(); UI operation failures remain terminal even if caught. Independent promise rejections are not globally monitored: a rejected chain that is not returned by an event handler can leave placeholder or partial values while the view remains ready. If using promises, return the complete chain from the event handler and handle initialization rejections locally. Ready means there is no pending work and a successful initialization or handled round was applied for the current parameter version; it does not establish that every generated calculation succeeded.

Each initialization or event has a 250 ms execution budget, a 32 MiB guest heap and a 256 KiB stack. A round can drain at most 256 pending microtask jobs and emit at most 64 patches. Exceeding either limit ends the activation. Input values have a 2,048-character limit; smaller authored maxlength bounds on text inputs and textareas are preserved; each complete input snapshot and patch batch must fit 16,000 characters and UTF-8 bytes. Register at most 192 distinct element/event pairs; registering the same pair replaces its handler. Startup and event watchdogs terminate an unresponsive Worker. These are guest execution limits; markup, CSS, input and output bounds separately limit renderer work. The Stop button ends the current activation. Failures reported to the host preserve a readable summary and display the limitation.

Styling

The view document follows the host's light or dark theme. When the host supplies standard MCP App style variables (Web Shell sends its color, font and radius tokens), the view copies them onto its root; otherwise it falls back to system colors. A base stylesheet styles headings, text, labels, form controls, buttons, tables and output with them and offers --gu-text, --gu-muted, --gu-surface, --gu-border, --gu-accent, --gu-danger and --gu-radius plus the panel, grid, stat, muted, danger and num classes. Generated CSS loads after the base stylesheet and can still override it; the writer skill asks for these variables instead of fixed colors or widths.

Applied parameter snapshots

The trusted Controller exposes a local captureSnapshot() interface for host integration. It returns a deeply frozen schema-version-1 parameter snapshot only when the current activation is ready; otherwise it reports not-ready, busy, dirty, stopped or error. Each snapshot carries its document identity, temporary activation identity, applied request sequence, dispatch parameter revision and post-apply parameter revision. The capture result also records the number of queue rejections observed at capture time.

Parameter revisions advance when the control ID set, value or checked state changes, including changes caused by patches. Same-value input/change notifications do not advance them; changing a value and changing it back does. Captures use browser-normalized values after the actual DOM update, remain unchanged by later edits, and retain the existing input bounds. Supported controls can include disabled or visually hidden values and values written by generated code; a snapshot is not user approval or a calculation correctness guarantee. It contains no derived output, HTML, source, guest closure state or persistence data. Generated code cannot access this interface. Snapshots do not restore interaction state.

Continue analysis with current parameters

In Qwen Code hosts that support it, the fixed App provides Continue analysis with current parameters outside the generated content. Apply changes with the view's update action first. Clicking this button captures the current applied parameters and sends them to the original session as one ordinary request; Web Shell shows the request as a labelled Interactive view values reference instead of the raw message. Editing controls alone never sends a message. Other hosts retain local interaction without this entry point being enabled.

The message includes the title, every supported control's string value and checked state, snapshot identity/revisions, and queue rejection count. Disabled, visually hidden and script-written values are included. The initial summary, source and derived results are excluded; values are data and calculations may need to be recomputed. An empty control set is explicitly identified. Rejected interactions are reported and are not replayed. The message is frozen when the button is clicked and must fit 16 KiB in both characters and UTF-8 bytes, including JSON escaping and instructions; nothing is truncated or split. The host's normal preparation, session ownership, busy-state and permission checks still apply.

The button stays disabled while awaiting the host. Accepted means the session received the request; analysis may still be running. When the host reports that nothing was sent (the session was busy, the request was refused, or it was cancelled), the card says so with the host's reason and offers the action again. Any other unconfirmed outcome is shown as such; check the original session first, because another submission could duplicate a request. No automatic retries occur. Stop, runtime failure or closing the card stops waiting; it cannot withdraw a dispatched request or cancel analysis already accepted by the session. Sending state is local to this activation and is not restored on reload.

Keep chatting about the view

When the host accepts model context (Web Shell does), the App sends the applied values after each change, once they have settled for 800 ms, using the MCP Apps ui/update-model-context request. Web Shell places them above the composer as one removable Interactive view values tag per card, replaced as the values change, so whatever the user types next carries the card's current state. Removing the tag leaves it out of that message; sending the explicit continue action clears it. Values are only offered after the user has interacted with the view, and never from a hidden or parked card.

Complete examples

These values are illustrative assumptions, not measured system performance or product recommendations. Both objects are complete tool arguments and can be passed directly to generated_ui_show.

Parameter simulation

Change arrivals, worker count or per-worker capacity. The calculation uses capacity = workers × per-worker rate and utilization = arrivals / capacity; it does not model queueing latency.

{
  "title": "Queue capacity planner",
  "summary": "At the initial settings, 4 workers at 25 requests/s each provide an estimated 100 requests/s. With 80 arrivals/s, utilization is 80%. These are illustrative steady-state assumptions; queueing latency is not modeled.",
  "html": "<section><p>Explore the supplied assumptions.</p><label for=\"arrivals\">Arrivals per second</label><input id=\"arrivals\" type=\"range\" min=\"0\" max=\"300\" value=\"80\"><label for=\"workers\">Workers</label><input id=\"workers\" type=\"number\" min=\"1\" max=\"20\" value=\"4\"><label for=\"rate\">Requests per second per worker</label><input id=\"rate\" type=\"number\" min=\"1\" max=\"100\" value=\"25\"><p id=\"capacity\"></p><p id=\"utilization\"></p><p id=\"balance\"></p><button id=\"recalculate\" type=\"button\">Recalculate</button></section>",
  "css": "",
  "javascript": "function bounded(id,min,max){var value=Number(ui.input(id).value);return Number.isFinite(value)?Math.max(min,Math.min(max,value)):min;} function update(){var arrivals=bounded('arrivals',0,300);var capacity=bounded('workers',1,20)*bounded('rate',1,100);ui.text('capacity','Estimated capacity: '+capacity.toFixed(0)+' requests/s');ui.text('utilization','Estimated utilization: '+(100*arrivals/capacity).toFixed(1)+'%');ui.text('balance',arrivals<=capacity?'Estimated spare capacity: '+(capacity-arrivals).toFixed(0)+' requests/s':'Arrivals exceed capacity by '+(arrivals-capacity).toFixed(0)+' requests/s');} ['arrivals','workers','rate'].forEach(function(id){ui.on(id,'input',update);});ui.on('recalculate','click',update);update();"
}

Weighted option comparison

Two fictional options are scored on responsiveness and operational simplicity, each on a 0–10 scale. Moving one weight keeps the combined weight at 100%. The ranking changes only with these explicit scores and weights.

{
  "title": "Compare two operating plans",
  "summary": "Using illustrative scores and the initial 60% responsiveness / 40% simplicity weights, Plan A scores 7.0 and Plan B scores 7.2. Plan B ranks slightly higher under these assumptions. Change the weight to explore the tradeoff.",
  "html": "<section><p>Illustrative scores; higher is better.</p><label for=\"weight\">Responsiveness weight</label><input id=\"weight\" type=\"range\" min=\"0\" max=\"100\" value=\"60\"><p id=\"weights\"></p><table><thead><tr><th>Plan</th><th>Responsiveness</th><th>Simplicity</th><th>Weighted score</th></tr></thead><tbody><tr><td>A</td><td>9</td><td>4</td><td id=\"score-a\"></td></tr><tr><td>B</td><td>6</td><td>9</td><td id=\"score-b\"></td></tr></tbody></table><p id=\"ranking\"></p></section>",
  "css": "",
  "javascript": "function update(){var raw=Number(ui.input('weight').value);var w=Number.isFinite(raw)?Math.max(0,Math.min(100,raw)):60;var a=(9*w+4*(100-w))/100;var b=(6*w+9*(100-w))/100;ui.text('weights','Responsiveness '+w+'%; simplicity '+(100-w)+'%');ui.text('score-a',a.toFixed(2));ui.text('score-b',b.toFixed(2));ui.html('ranking',a===b?'The plans are tied under these assumptions.':'<strong>Plan '+(a>b?'A':'B')+'</strong> ranks higher under these assumptions.');}ui.on('weight','input',update);update();"
}

Replay and failure behavior

The server writes no document cache. Before returning a snapshot, it runs the generated JavaScript's initialization once in the same bounded QuickJS interpreter the view uses, with a stand-in ui API, the markup's initial input values, and no host, file, network or tool access. Syntax errors, exceptions from the initial calculation, initial updates to missing element IDs and markup the view rejects (duplicate or invalid IDs, controls without IDs, unsupported input types, multiple selects) return a VIEW_ERROR tool result naming the problem, with its line and column where available, so the model can correct the document. Failures that only occur after user input are not detected there; the App names ordinary script errors and offers to send them to the session for correction after host review. It returns a versioned snapshot with a SHA-256 identity. Public text and structured output contain the title, summary and identity; source travels in the App-only result metadata. Titles and summaries are normalized before hashing and shared unchanged by the App, readable fallback and structured output. Normalization removes control characters, Unicode formatting characters and line/paragraph separators, preserving newline, tab and the U+200C/U+200D joiners used in text and emoji. Each normalized title and summary must contain text beyond whitespace and those joiners; empty or blank-only fields are rejected during creation and replay. HTML, CSS and JavaScript source are unchanged. Previously saved snapshots with unsupported display characters or blank-only titles/summaries are rejected with a visible limitation; other clean snapshots retain their identities. Resource reads always return the fixed trusted bootstrap, independent of earlier show calls. The browser validates the complete snapshot and identity before execution.

Daemon/Web Shell history can replay the saved initial snapshot without fetching the generating MCP server again. Two tool calls remain independent, including two copies of the same document. A later show call does not update an earlier card. Stop remains terminal for the current activation, including if pressed before a result arrives; duplicate final notifications cannot restart it. The card frame follows the host locale (English and Simplified Chinese), sizes the view to its content without its own border or scroll area, collapses the summary to two lines, and notes after the first interaction that refreshing the page restarts from the initial values. A stopped view stays visible under an overlay that points to reopening it. When the host reports that a review was cancelled, expired or refused before the session accepted it, the card says nothing was sent and offers the same action again, keeping the warning of an earlier attempt that may still be in flight. Frame failures follow the host locale; errors from the generated document are shown as written.

The bundled feature keeps a card's live frame and Worker while scrolling through the transcript or switching sessions within the same workspace and browser page, including leaving for New task, when the browser supports state-preserving DOM moves. Applied results, unapplied inputs and local runtime state remain in that activation. Canonical workspace identity survives transport reconnects. Hidden cards cannot call host tools or submit messages; returning requires fresh session authorization. A pending review is cancelled when its card leaves the active view, while an already dispatched or accepted request cannot be withdrawn and is never automatically resent.

At most four detached cards are retained, each for up to 15 minutes. Eviction, changed resources or permissions, confirmed policy revocation (including a disabled capability receipt), authentication failure, session/message deletion, rewind, a daemon epoch change, changing workspace or host, and unsupported browser moves release the affected activation. A later enabled receipt cannot erase a confirmed revocation, even before React commits; reopening remains explicit. Missing or failed capability reads revoke authority without confirming policy revocation. Authentication recovery preserves released-card records and requires explicit reopening. Released cards show Reopen interactive view; Reopen and reset is also available on a retained card, including after Stop or an error. Both start from the original content and discard previous interactions. Check the original session before submitting again: resetting the view does not cancel analysis already sent. After the page has tracked 128 card identities, additional cards require explicit opening and are not retained.

This retention is limited to eligible bundled cards with a known daemon producer and epoch. Ordinary MCP Apps and manually installed extensions keep their existing lifecycle. Refreshing or closing the page, exporting a transcript, or forking a session does not save or transfer a live activation. There is no persistent interaction-state storage.

The existing host can degrade rich cards to their text fallback when history or delivery windows exceed their budgets. Terminal history does not retain interactive App payloads. A host without MCP Apps retains the readable summary. Missing Worker/WebAssembly/DecompressionStream support, invalid or dropped snapshot metadata, digest mismatch, invalid controls or exhausted runtime budgets produces a visible limitation instead of re-fetching or executing incomplete source. Unsupported markup is removed by the sanitizer. This extension does not promise streaming, saved interaction state or unlimited rich history.

Verification

From this integration directory:

corepack pnpm run test
corepack pnpm run typecheck

The tests exercise producer bounds and identity, the actual bundled stdio server, fixed-resource behavior, independent snapshots, Unicode and escaping, and execution from a packed tarball outside the source tree. Browser/runtime and daemon acceptance must separately verify the displayed calculations, interaction, stop/timeout behavior and sandbox isolation. A valid document or a green protocol test does not establish that the generated model's assumptions or calculations are correct.