Commit Graph
11 Commits
Author SHA1 Message Date
4ekuct25andСергей Панченко 68f181b885 feat(web-shell): add Russian locale for the Goal card and Goals dialog (#13685)
* feat(web-shell): add Russian locale for the Goal card and Goals dialog

The web-shell i18n shipped only en and zh-CN and collapsed every other
browser locale (e.g. ru-RU) to en in normalizeLanguage, so a Russian
user saw the Goal card and the Goals dialog in English around a
Russian objective.

Add a partial ru catalog for the goal.*/goals.* keys with Russian
pluralisation (ход/хода/ходов, токен/токена/токенов,
активная цель/активные цели/активных целей). getTranslator already
falls back per key to EN (messages[key] ?? EN[key] ?? key), and the
partial catalog relies on that for every other key — the behaviour this
locale has been running with as an installer patch.

- WEB_SHELL_LANGUAGES / LANGUAGE_LABELS: add 'ru' / 'Русский [ru]' to
  the settings picker
- normalizeLanguage: accept ru / ru-ru / russian / русский
- languageSettingToWebShellLanguage: accept the same spellings for the
  daemon's general.language setting
- WebShellProps.language / WebShellTranscriptProps.language: accept the
  'ru' and 'ru-ru' raw values
- surfaces with their own WebShellLanguage-keyed copy tables
  (StandaloneAuth, RootErrorFallback, conversation search, desktop
  relay) reuse the EN copy for ru, same partial-catalog policy
- re-measure the transcript entry size budget for the catalog
  (+4,649 to 1,325,015; ceiling raised to 1,328,000)

Part of #13391

* fix(web-shell): goals.dropped count and ru_* language normalization

goals.dropped now renders the session count with the correct Russian
plural form (it previously dropped the ${count} interpolation and
showed a bare phrase). The ru language normalizers match the ru prefix
instead of a fixed spelling list, so region-qualified values
(ru-RU, ru-UA, ru-KZ, …) and the underscore form resolve to the
partial ru catalog instead of falling through to en. Add count and
plural pins for goals.dropped and region-form pins for both
normalizers.

* chore: empty commit to retrigger CI and review

* feat(core): Goal verifier verdict inherits the session output language

The verifier's 'Last check:' reason is user-visible prose (#13391 part 2), so it should honor the session output-language preference. It was the only goal-related side query still passing skipOutputLanguagePreference: true; removing the flag makes runSideQuery append the preference. The decision enum stays schema-bound - only the reason prose localizes. Also re-measures the merged web-shell transcript entry (1,334,649 chars) and sets its size ceiling to 1_337_000 with the matching budget narrative.

* fix(web-shell): drop stray conflict marker left by the main merge

* fix(web-shell): accept /language ui ru and derive language listings from WEB_SHELL_LANGUAGES

* fix(web-shell): render goals.dropped with the count and the dialog's session term

* fix(web-shell): make the desktop relay copy table total over WebShellLanguage

* fix(vscode-ide-companion): let the web shell resolve its own UI language

* docs(web-shell): document ru in the language prop unions

* test(web-shell): pin tokensOfBudget pluralising on the budget

* revert(core): move the Goal verifier language change to companion PR #15806

The output-language change for the Goal verifier verdict is split out to
its own PR (QwenLM/qwen-code#15806) so the core change gets dedicated
review under the small-core-change rule. This PR is now web-shell only.

* test(web-shell): pin the /language error and usage copy to the shipped set

* fix(web-shell): restore the DesktopRelayControl header truncated in 705a3b799

The staged hunk for the relay copy-table change cut off the top of the
file: the license header, all imports and the DesktopRelayPhase/
DesktopRelayBlocker types were missing from the committed file, leaving
it uncompilable (tsc: 63 errors). Restore the header verbatim from the
parent commit (c5ff652b5), re-indent the extracted EN_COPY table, and
fold the ternary-free useCallback call to the single line prettier wants.
The semantic change of 705a3b799 (en table extracted to EN_COPY,
explicit ru: EN_COPY entry, ternary-free COPY[language][key] lookup)
is preserved unchanged.

* style(web-shell): prettier 3.6.1 formatting for the /language gate and README language row

---------

Co-authored-by: Сергей Панченко <[email protected]>
2026-10-29 12:49:34 +00:00
callmeYe 8aaaaf2524 feat(web-shell): add footnote previews and per-turn sources (#11480)
* feat(web-shell): add grouped footnote previews

* fix(web-shell): synchronize footnote type dependency lockfile

* feat(web-shell): complete source footnote previews

* fix(web-shell): stabilize citation footer previews

* fix(web-shell): align citation controls

* fix(web-shell): optically align citation icon

* feat(web-shell): generalize footnotes and expose host icon resolvers

* fix(web-shell): align demo citation artwork with footer icons

* feat(web-shell): add framework-neutral footnote preview content slot

* feat(web-shell): show turn sources independently of footnotes

* test(web-shell): use same-origin footnote thumbnail fixture

* fix(web-shell): preserve source focus and nested footnotes

* test(web-shell): isolate footnote model-list fixtures

* chore: regenerate pnpm lockfile for citation types

* fix(web-shell): reuse committed turn sources while streaming

* docs(web-shell): clarify panel-only source visibility

* fix(web-shell): isolate history sources and restore hover dismissal

* fix(web-shell): preserve single formula text in footnote previews

* fix(web-shell): restore declined footnote pages on return
2026-09-16 03:05:46 +00:00
ytahdnand钉萁 668df522c0 feat(web-shell): customize artifact icons (#11247)
* feat(web-shell): customize artifact icons

* fix(web-shell): preserve artifact icon fallbacks

* fix(web-shell): improve artifact fallbacks

---------

Co-authored-by: 钉萁 <[email protected]>
2026-09-07 09:27:02 +00:00
db8897d567 fix(export): shrink the exported transcript renderer to what a read-only transcript needs (#11038)
* fix(export): stop inlining the interactive Web Shell runtime in exported HTML

/export html builds document-main.tsx with esbuild bundle:true and inlines
the result into every exported file. The entry imported WebShellTranscript
from the @qwen-code/web-shell package root, whose facade also reaches App,
the daemon providers, the editor/terminal chrome and the full injected
component stylesheet, so each export carried 19,523,259 runtime bytes
(62x the legacy renderer) even for an empty session (#11031).

Add a transcript-only subpath entry (@qwen-code/web-shell/transcript,
mirroring the existing ./daemon-react-sdk subpath and the SDK's
./daemon/transcript pattern) that exports only the read-only transcript
renderer, and point document-main.tsx at it. The entry is built in its own
rollup run so it carries only the stylesheet reachable from transcript
rendering instead of the whole component sheet. Exports stay fully
self-contained: CSP, nonce handling and offline rendering are unchanged.

Also add a hard size budget on the inlined document runtime in the
template build, mirroring the MAX_*_BROWSER_BUNDLE_BYTES assertions in
packages/sdk-typescript/scripts/build.js. The limit sits below the
regression value so a repeat fails the build instead of landing silently.

Measured on the generated document template (empty session):
- before: 19,525,807 bytes raw / 4,775,943 gzip (runtime 19,523,259)
- after:  17,966,485 bytes raw / 4,512,650 gzip (runtime 17,963,937)

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmtn7fhrmj3

* fix(export): drop dead Shiki and CodeMirror from the export runtime

The transcript-only entry stopped the interactive shell from reaching the
inlined document runtime, but two large graphs still did, and the size
budget could not see either of them.

- Shiki (~9.7 MB of pre-minify inputs, the single largest) is unreachable
  in an export: `CodeBlock` returns before touching the highlighter when
  `renderMode === 'document'` and always renders a plain `<pre>`. It is a
  static import of `codeHighlighter.ts`, so esbuild cannot drop it. The
  document build now resolves `shiki` / `@shikijs/*` to a stub. Its
  Oniguruma WASM engine could not have started under the export CSP
  (`script-src 'nonce-…'`, no `'wasm-unsafe-eval'`) in any case.
- CodeMirror (~1 MB) still reached the transcript entry: `UserMessage`
  imported three composer-tag string getters from `hooks/useComposerCore.ts`,
  which pulls the whole editor at top level. The getters move to
  `utils/composerTag.ts` (editor-free, and already imported by
  `UserMessage`); `useComposerCore` re-exports them for existing callers.

Replace the byte-cap-only ratchet with a structural one: the document build
now emits a metafile and fails when a forbidden module graph appears in the
inputs (Shiki, the web-shell package root, CodeMirror), naming the reason.
The byte cap stays as a coarse backstop and still needs re-measuring.

Also fix a latent collision introduced by building the transcript entry in
its own rollup run: both entries injected their stylesheet under the same
`data-qwen-web-shell="component"` key, so a host importing both would keep
whichever loaded first and silently lose the other's rules. Injection is now
keyed per entry, and shadow-root style adoption concatenates every match.

Refs #11031

* docs(verification): add the export-html runtime size verification plan

The change to the inlined `/export html` runtime rests on measurements that
cannot be taken on the machine that wrote it. Rather than leave that as a
caveat in the PR description, commit the plan to the branch so any
build-capable machine gets it by cloning: exact commands, the reference
numbers to compare against and whose machine they came from, the premise the
Shiki removal rests on plus a way to falsify it independently, the two budget
constants that are knowingly stale, and what to report back.

Follows the docs/verification/abort-controller-refactor layout; results belong
next to it as results.md.

Refs #11031

* test(web-shell): scope the transcript entry guard to its JS graph

The not.toContain('vaul') guard ran against the whole bundle, but
injectCssModules prepends the Tailwind-generated stylesheet as a
single-line __qwenWebShellCss constant, and Tailwind v4 compiles
classes from every scanned source file regardless of the entry's
module graph — so drawer.tsx's data-[vaul-drawer-direction=…] variant
selectors land in the transcript entry's CSS even though no transcript
JS imports vaul (verified: 64 'vaul' occurrences in the injected CSS
line, 0 in the JS remainder, no from"vaul" import specifier).

Strip the injected CSS line before the graph guards. If the injection
shape ever changes, the replace() is a no-op and the checks fail
loudly rather than passing falsely.

* fix(export): drop the dead echarts runtime from the export bundle

CI measured the merged branch at 8,456,076 runtime bytes, and named the
inputs: `echarts 3841596` and `zrender 624992` are still the largest single
block in it.

They arrive the same way mermaid does. `@datafe-open/markdown-chart-echarts`
ends `createEChartsRenderer` with

    const loadECharts = options.loadECharts ?? (async () => await import('echarts'));

and this repository never takes that default: `MarkdownChartRenderer` always
passes a `loadECharts`, because `adaptLegacyRuntimeLoader` returns a function
that throws 'Chart runtime is unavailable.' when no `loadEcharts` prop was
supplied, and no call site in `packages/web-shell/client/` supplies one. So the
fallback is dead in this codebase — but a bundler cannot prove that, and the
export build is esbuild `format: 'iife'` with a single outfile, which cannot
code-split, so the dynamic import is flattened straight into the renderer.

Resolves `echarts` to a stub on exactly the reasoning the Shiki stub already
uses in this file — unreachable in document mode — and adds echarts/zrender to
FORBIDDEN_DOCUMENT_INPUTS so it cannot come back unnoticed. Chart blocks in an
exported transcript already could not render; they threw
'Chart runtime is unavailable.' before this change and do the same after.
Whether exports *should* render charts and diagrams is #11091, and this does
not prejudge it: giving the renderer a real runtime stays a deliberate act.

Also lowers the runtime budget from 19,000,000 to 8,700,000 using the CI
measurement above. It is still loose, because the echarts stub in this same
commit lowers the real number again — tighten it from the next green run
rather than from a guess. The structural guard is the load-bearing check.

* fix(web-shell): make the transcript entry's "no daemon providers" claim true

Review found the entry docblock over-claiming. `WebShellTranscript.tsx`
value-imported `transcriptBlocksToLocalizedMessages` from `hooks/useMessages`,
and that module value-imports `useConnection` / `useTranscriptBlocks` /
`useWorkspace` from the `daemon-react-sdk` barrel — so `dist/transcript.js`
still carried the provider guards, including
`useDaemonActions must be used within DaemonSessionProvider`.

Gives that helper the same leaf-module treatment as the composer tags: the
pure projection (and the `Translator` type it needs) moves to
`adapters/localizedMessages.ts`, which imports nothing from the daemon SDK
barrel, and `useMessages.ts` re-exports both so existing callers and its own
test are unaffected. `build-artifact.test.ts` now asserts `dist/transcript.js`
contains neither `DaemonSessionProvider` nor `DaemonWorkspaceProvider`, so the
docblock's claim is pinned rather than asserted in prose.

Also fixes the read-only recipe in `packages/web-shell/README.md`, which still
taught the root import this PR makes wrong — a host following it would ship
the interactive shell, which the reviewer measured as landing a document at
~8.8 MB, past the new cap. Same for the example in the read-only transcript
design doc.

Tightens the runtime budget to 7,400,000 / 7,300,000 warning, from the
reviewer's local measurement of 7,275,173 bytes with the echarts stub in place
(CI's 8,456,076 predates it).

* fix(web-shell): drop the now-unused adapter import from useMessages

The projection that used transcriptBlocksToDaemonMessages moved to
adapters/localizedMessages.ts in 1d94060f5, leaving the import behind.
Lint & Static caught it:

  14:10  error  'transcriptBlocksToDaemonMessages' is defined but never used

* fix(web-shell): assert what the transcript entry delivers, not what it doesn't

The guard added in 1d94060f5 was red against a clean build. Moving
`transcriptBlocksToLocalizedMessages` out of `hooks/useMessages` was correct
but it was one of several paths into the daemon barrel, and not the load-
bearing one: review's static walk of the entry's value-import graph found
three more, through components the transcript genuinely renders —
`useArtifactWorkspaceTarget.ts` (`useWorkspace`, `useWorkspaceActions`),
`McpStatusMessage.tsx` (`useMcp`) and `TasksStatusMessage.tsx` (`useActions`).
Every one of those barrel aliases resolves to a strict hook, so the provider
guards ship no matter which module the import names. Consistent with that,
`dist/transcript.js` came out 90 bytes larger after the extraction, not
smaller.

Asserting `DaemonSessionProvider` is absent therefore asserted something this
entry does not deliver. Replaced with a bound on the transcript JS remainder —
1,140,948 measured bytes against 7,021,715 for `dist/index.js`, ceiling
1,300,000 — which pins the property the entry actually exists for.

The entry docblock now says the same thing: no `App`, no composer, no
editor/terminal chrome, and the daemon hook runtime named as a known residual
with #11100 as its home. That issue also carries the question this raised —
`TasksStatusMessage` calls `useActions()` unconditionally and
`WebShellTranscript` mounts no provider, so whether an export containing a
tasks-status block renders or throws depends on what the document projector
filters, and nobody has checked.

* fix(web-shell): preserve KaTeX cascade in transcript bundle

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-09-06 06:15:42 +00:00
7f7bce3174 feat: chat transcript mr2a html export (#10076)
* feat(transcript): add cross-host document export pipeline

Establish a shared transcript model and document-mode projection so Web
Shell, VS Code, and HTML export can consume the same stable conversation
semantics without changing interactive rendering.

- Preserve daemon and ACP segment identity across replay and normalization
- Add export-safe previews and a versioned transcript document builder
- Add document-mode Web Shell rendering with bounded Mermaid processing
- Lock direct-daemon and ACP behavior with contract fixtures
- Cover render and export equivalence in integration tests

* feat(transcript): wire real VS Code and HTML export consumers

Move transcript prevalidation onto real product paths while preserving
the legacy VS Code timeline as the default fallback.

- Add version-bound document HTML with strict CSP and schema checks
- Add opt-in ACP projection with stable source identity
- Reuse product export code across hosts and the integration runner
- Remove test-only production probes and consolidate shared validation

Note: Browser, host-action, VSIX, and packaging gates remain pending.

* build(deps): sync transcript workspace dependencies

Keep the npm lockfile aligned with the VS Code and HTML export package
manifests added by the transcript consumer migration.

- Lock the VS Code Web Shell workspace dependency
- Lock Web Templates SDK, Web Shell, and React build dependencies

* fix(transcript): harden export and identity paths

Resolve review findings across document export and the VS Code ACP
timeline while preserving default interactive and readonly semantics.

- Preserve stable text and non-text identity across live and replay
- Harden export projection, budgets, URL handling, CSP, and nonces
- Make document rendering complete, inert, and browser-validated
- Keep VS Code transcript scope, theme, copy, and flags reactive
- Restore fixture, schema, hash, and compatibility contract locks

Note: The overall gate remains failed pending VSIX, host-action, and
packaged-artifact evidence.

* fix(transcript): harden identity and document export

Close the latest review findings across transcript identity, VS Code
rehydration, and the HTML export security boundary.

- Terminate discrete ACP segments and bind automatic turn provenance
- Reset VS Code transcript scopes across replay and reconnect lifecycles
- Align Markdown sanitization and envelope budgets with document rendering
- Run browser gates with Chromium and complete third-party notices

* fix(transcript): simplify adapters and preserve segment boundaries

Reduce the shared transcript review surface while keeping the VS Code and
HTML Export consumers and their security gates intact.

- Merge VS Code feature state into the ACP transcript hook
- Collect source identity once and strip it at the compatibility boundary
- Remove test-only export adapters, gate reports, and duplicate helpers
- Keep shell output separated when stable producer segments change
- Mark third-party notices as generated review content

* fix(transcript): address cross-host review regressions

* fix(transcript): harden export sanitization and user identity

Close the latest review findings around document resource safety and
recorded-user replay consistency.

- Redact local home paths structurally without corrupting remote URLs
- Sanitize nested Markdown images inside otherwise safe links
- Advance merged segment provenance without duplicating separators
- Keep recorded-user stable IDs anchored to durable record identity

* fix(web-shell): restore agent-only group predicate

* refactor(transcript): split HTML export from VS Code migration

Keep MR2A focused on the document export pipeline while preserving the legacy VS Code timeline.

- Retain VS Code /export html as a real document consumer
- Move live transcript adapters, identity wiring, and feature flag to MR2B
- Remove the direct Web Shell dependency and restore baseline notices
- Keep candidate identity evidence in integration-only helpers
- Document the MR1, MR2A, and MR2B delivery boundaries

* fix(web-shell): keep compact agent summaries separate

Preserve synthetic compact summary rows when parallel-agent grouping runs
after compact tool-group aggregation.

- Exclude summary rows from foreground agent-only grouping
- Exclude summary rows from background agent-only grouping

* fix(core): remove duplicate telemetry test mock

* refactor(sdk): share todo preview render fallback

* fix(web-shell): align document subtools with main

* fix(transcript): address HTML export review findings

* fix(transcript): address export review findings

* fix(transcript): close export boundary bypasses

Harden the document export boundary after the latest review found
mixed-path and Markdown parser/scanner gaps.

- Normalize dot segments and mixed separators before home redaction
- Align Markdown preflight with fences, HTML comments, and delimiters
- Remove the internal review report from the tracked repository

* fix(transcript): close export review findings

Close three Critical review findings in the document export and safe
transcript projection paths.

- Share percent-token decoding between home-path redaction and checks
- Reject parser/scanner divergence before Markdown parsing can stall
- Preserve todo entries carried by the document-safe projection
- Add regression witnesses for each reported failure mode

* fix(export): restore document parity and harden projections

Restore HTML export parity without weakening the sealed document
boundary.

- Reject recursively encoded and split home-path structures
- Keep synthesized todo identities unique from authored values
- Restore safe metadata, theme, and document-wide expansion controls

* fix(export): close home-path redaction entrances and restore safe-projection signals

- safePath: detect home segments at any depth (nested /usr/home, /export/home, UNC shares map to [home])
- redactor/validator: use a precise URL pattern (authority+path only) so URL-adjacent and URL-query home paths are no longer absorbed into the remote-URL span; allow quoted username segments
- exporter: dynamic-import the export API in main() with an ERR_MODULE_NOT_FOUND guard and a clear build-precondition message
- safe projection: propagate executionMode from block.background so foreground subagent launches stay separate and keep intervening thinking

* fix(export): close residual home-path leaks

Harden the sealed document boundary against path spellings that could
survive the existing surgical redaction pass.

- Detect residual home components independently after redaction
- Handle spaced usernames and URL-adjacent local paths
- Exempt validated raster thumbnails from visible-text scanning

* fix(export): handle Markdown fences across containers

Prevent document export from aborting or dropping valid code when
fenced Markdown uses non-LF endings or blockquote containers.

- Demote rich fences with CRLF and CR opening lines
- Track CommonMark blockquote ownership with bounded linear scanning
- Cover quoted, nested, HTML-container, and line-ending regressions

* fix(export): unify home-path grammar and keep cancelled safe results

Round 8 review found the export home-path boundary still open at two
entrances, and the document safe projection silently destroying a
cancelled sub-agent's terminal result.

- Derive the residual home-path detector from the redaction grammar
  instead of hand-modelling it, and redact bare plus line-initial
  relative home roots so ordinary text is no longer discarded wholesale
- Consume each remote URL as one token: verified remote paths pass
  through byte-identical, while a token whose ownership stays ambiguous
  after a delimiter or a malformed authority degrades as a whole
- Return typed omission signals from home redaction so encoded-content
  and URL degradation always set truncated/complete with a diagnostic,
  instead of inferring state from a marker string a transcript can forge
- Preserve a string terminal result when stamping the cancelled
  background-agent marker, and project that cancellation to failed plus
  wasCancelled only on the explicit safe projection path

Note: interactive and readonly callers keep the existing raw and status
compatibility, so a cancelled background agent still reconciles to
completed there.

* fix(export): strip URL credentials from exported command previews

`user_shell.command` and the `command` tool preview went into the document
through `budget.plainText`, which only applies home-path redaction and the byte
budget. A reachable `curl https://user:[email protected]/file?token=secret`
therefore carried both the URL userinfo and the secret query into the exported
JSON and the shareable HTML, against the contract that credentials never enter
the document.

Reuses the projector's own `normalizeNavigableUrl` — the same helper the typed
`web_fetch.url` and repository paths already go through, which clears
`username`, `password`, `search` and `hash` — rather than reaching for the
SDK's `sanitizeUrlsInText`, which is module-private in `daemon/ui/render.ts`
and would have needed a new public export. A command is free text that embeds a
URL rather than being one, so the new helper rewrites each embedded match and
reports `url_sanitized` / `url_rejected` through the existing diagnostics
counter.

Runs before `budget.plainText`: a URL truncated first may no longer match the
pattern, and the truncation boundary should be measured on the text that
actually ships.

Scope is http(s), matching `normalizeNavigableUrl`. A credential in a
non-navigable scheme (`ssh user:pw@host`) or a bare flag (`-pSECRET`) is not
covered; that needs argument-aware redaction, which this projector does not
attempt. The limit is stated on the helper so the contract is not read as
stronger than the code.

* test(web-shell): keep the render-mode provider across session-switch rerenders

Two of main's session-guard tests failed on this branch:
`ignores a stale polling response from the previous session` and
`ignores a workflow action that settles after switching sessions`, both with a
spy that must not fire being called once.

The component was not at fault. Its diff against main is only `documentMode`
guards, and `documentMode` is false throughout these tests, so the interactive
paths are main's unchanged. The cause is the shape of the React tree: this
branch's mount helper renders
`I18nProvider > TranscriptRenderModeProvider > TasksStatusMessage`, while those
tests simulate a session switch by calling `root.render` again with
`I18nProvider > TasksStatusMessage`. The element type at that position changes,
so React unmounts the subtree and mounts a fresh component instead of updating
it — which resets `expectedSessionIdRef` and re-runs the polling effect, and
the guard those tests exist to pin never gets the chance to suppress anything.

Wrapping the three bare `root.render` calls keeps the tree shape stable across
the rerender, with the same `interactive` value the helper defaults to, so the
component updates in place as the tests intend.

ToolGroup.test.tsx has the same bare-render shape in places but its tests pass —
those sites are fresh mounts, where a remount is not observable — so they are
left alone rather than churned.

* fix(web-shell): declare documentMode in the two workflow callback deps

`lint:ci` runs eslint with `--max-warnings 0`, so the two
`react-hooks/exhaustive-deps` warnings failed the job outright:

  TasksStatusMessage.tsx
    610:5  React Hook useCallback has a missing dependency: 'documentMode'
    655:5  React Hook useCallback has a missing dependency: 'documentMode'

`handleWorkflowAction` and `handleWorkflowHistoryDelete` gained a
`if (documentMode || busy) return;` guard during the merge, but their dependency
arrays sat outside the conflict regions and so kept main's list. Adding the
dependency is also the correct fix rather than a lint appeasement: both
callbacks close over `documentMode` now, and a stale closure would let a
document render run an action it must not.

Swept the file afterwards for any other hook whose body reads `documentMode`
without declaring it; these two were the only ones.

* fix(export): validate URL boundaries and raster signatures

Close verified document admission gaps without changing live transcript
semantics or discarding legitimate remote path examples.

- Reject missing and encoded home-path authorities before URL repair
- Check raster MIME signatures in the producer and envelope validator
- Remove redundant separator repetition and pin bounded regressions
- Preserve normal remote URLs and record undecidable decoding as loss

Note: Signatures check format headers, not full image or secret safety.

* ci: record e2e.yml growth in the workflow size baseline

e2e.yml is now 30022 bytes: 3610 bytes of drift already merged into
main within the 4096-byte allowance, plus this PR's transcript
document browser gate (the dedicated gate step, the
chat-transcript-document exclusions, and the nested Playwright CLI
install). Bump the ratchet in this PR as the gate requests, so the
growth stays a reviewed line.

Co-authored-by: Qwen-Coder <[email protected]>

* test(web-shell): scope workflow history visual assertion

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: heyang.why <[email protected]>
Co-authored-by: 易良 <[email protected]>
Co-authored-by: jinjing.zzj <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
2026-09-04 14:36:35 +00:00
48645c56f8 feat(web-shell): make compact view the only mode (#9993)
* feat(web-shell): make compact view the only mode

Remove the ui.compactMode toggle (Ctrl+O shortcut, settings persistence,
help entry, i18n copy) and fix the compact rendering on for every message
surface via a single root CompactModeContext provider — main chat, split
panes, subagent detail panel and the drawer variant. The daemon-side
setting registration stays untouched; the web shell keeps it hidden from
the settings panel.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): address compact-mode retirement review feedback (#9993)

- Mark ui.compactMode retired everywhere in the settings docs and schema
  description (regenerated), matching the always-on compact view; mark the
  long-gone ui.compactInline row as removed.
- Keep Ctrl+O suppressed globally after the toggle removal so the key never
  falls through to the browser's Open File dialog, with a pinning unit test.
- Add discriminating coverage: a settings-panel test that fails if
  ui.compactMode leaves HIDDEN_SETTING_KEYS, and an e2e spec asserting the
  merged compact summary row so flipping the app-level provider fails.

* fix(web-shell): address round-2 compact-mode retirement review (#9993)

Co-authored-by: Qwen-Coder <[email protected]>

* fix(web-shell): revert out-of-footprint test-config guard (#9993)

The verification gate rejected the round-2 commit because it added
dangerouslyIgnoreUnhandledErrors to packages/web-shell/vitest.config.ts,
a test-config file this PR never legitimately touched. Review feedback
cannot authorize changes to CI/verification machinery, so revert the
file to main. The failing Windows/macOS test lanes the guard targeted
are escalated to a maintainer as an open question instead.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-26 01:43:40 +00:00
Dragon 7a9aa5cc0a refactor(web-shell): extract shared contexts (#9954)
* refactor(web-shell): extract shared contexts

* refactor(web-shell): reuse TodoContextsProvider in transcript view

* test(web-shell): forward todo props through mocked TodoContextsProvider

The mock previously declared only { children } and hardcoded empty Maps,
silently discarding the timeline/details props the component passes.
Forward them (with empty-Map fallbacks) so in-tree consumers observe the
component's derived todo data, matching the pre-refactor wiring.
2026-08-25 11:37:59 +00:00
易良 5b3830b46c feat(vscode-ide-companion): adopt WebShell transcript as the default timeline (#9719)
* feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag

Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off).

The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged.

* fix(vscode-ide-companion): grant wasm-unsafe-eval for shiki WASM when WebShell transcript enabled

* feat(vscode-ide-companion): adopt WebShell transcript as default timeline

Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting).

The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM.

* fix(vscode-ide-companion): reset WebShell transcript state on session switch

The experimental useAcpTranscript hook only consumed transcriptUpdate
messages, so its reducer state survived session boundaries. When the
extension switched sessions it kept the webview mounted and replayed the
newly-selected session through ACP, causing the previous session's blocks
to merge with the new replay (e.g. user text "alpha" from session A leaked
into session B as "alphabeta").

Reset both the reducer state and the rendered blocks on the same
boundaries the legacy message flow uses: qwenSessionSwitched (sent before
the ACP replay of the selected session) and conversationCleared (new
session). Adds a regression test that replays two sessions with a switch
between them.

* fix(vscode-ide-companion): harden WebShell transcript session boundaries

- reset the transcript on `conversationLoaded` too, closing the same
  cross-session leak the previous commit fixed for `qwenSessionSwitched`
  and `conversationCleared` (agent reconnect posts only this boundary)
- track the active session id and drop late `transcriptUpdate` frames
  whose `sessionId` no longer matches, so a previous session's trailing
  frames cannot contaminate the next session's timeline
- seed the transcript from cached messages carried by
  `qwenSessionSwitched` so offline restores and load-failure fallbacks
  render their history instead of a blank timeline
- dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the
  final assistant/thought block of a turn (or history replay) does not
  stay `streaming: true` forever

* fix(vscode-ide-companion): adopt live ACP session id after load-failure fallback

* fix(vscode-ide-companion): echo user prompt into WebShell transcript

* fix(vscode-ide-companion): keep WebShell transcript expanded and clear of the composer

* fix(vscode-ide-companion): surface local error and interrupt notices in the transcript area

* fix(vscode-ide-companion): restore file-link opening from the WebShell transcript

* fix(vscode-ide-companion): restore contributed copy commands for the WebShell transcript

* fix(vscode-ide-companion): add localOnly marker to TextMessage state type

* fix(vscode-ide-companion): restore /insight progress card and report link in the transcript UI

* fix(vscode-ide-companion): finalize in-flight tools on timeout and pin session-switch seeding guard

Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN).

* fix(vscode-ide-companion): remove unreachable editMessage backend and dead submit options

The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b).

* fix(vscode-ide-companion): drop write-only loadingMessage bookkeeping

The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time (bd09e19d86), so the loadingMessage string was write-only dead state. Keep the isWaitingForResponse flag (submit gating / cancel) and pin its API surface (R1-19 direction b).

* fix(vscode-ide-companion): align waiting-flag pin test with the argument-less setter

* fix(vscode-ide-companion): echo attached images into the transcript timeline

The prompt carries pasted/attached images as ACP resource_link blocks,
which the transcript reducer cannot render (no inline data), so user
images vanished from the timeline while the attach path stayed alive.
Read each saved prompt image back from disk and echo it alongside the
text echo as an inline user_message_chunk image part (the daemon-echo
content shape), which the shared reducer folds into the user block and
the WebShell renderer already displays. Unreadable images are skipped
without breaking the send.

* fix(vscode-ide-companion): track live VS Code theme for the transcript

webShellTheme was snapshotted once at mount via useMemo with an empty
dependency array, so switching the VS Code color theme left the
timeline on the stale theme (VS Code updates data-vscode-theme-kind on
<body> in place without reloading the webview). Hold the theme in state
and refresh it with a MutationObserver on the body theme attributes.

* fix(vscode-ide-companion): copy every transcript block kind and map ambiguous row keys

- Copy All Messages now includes tool, shell, user_shell, and status
  blocks via getBlockCopyText, matching the pre-PR copyAllMessages
  handler which included formatted tool calls (review 5001842059 S-1).
- findBlockByRowKey prefers an exact id match and otherwise the longest
  matching block id, so one block id that dash-prefixes a sibling (e.g.
  `a` vs `a-1`) can no longer capture the sibling's row key (S-4).

* fix(vscode-ide-companion): drop whitespace-only cached transcript rows

cachedMessageToNotification rejected empty strings but admitted
whitespace-only content, which the reducer turns into an empty block
when seeding history from cached rows. Reject content that trims to
nothing (review 5001842059 S-2).

* fix(vscode-ide-companion): ship missing third-party notices in NOTICES.txt

Extend generate-notices.js so the regenerated NOTICES.txt carries the
attribution texts it previously only pointed at or dropped:

- Append license files from a package's licenses/ directory (echarts'
  Apache LICENSE references licenses/LICENSE-d3 for its embedded
  d3-derived files; the BSD-3-Clause text is now shipped).
- Append a package's NOTICE file when present (Apache-2.0 §4(d)),
  covering echarts' Apache Software Foundation attribution.
- Accept string-form package.json repository values (full URLs and
  GitHub shorthand) instead of emitting "(No repository found)".
- Fall back to the standard MIT text (copyright holder from package.json
  metadata) for MIT-declared packages that ship no license file.

* fix(vscode-ide-companion): show a recoverable error state when the transcript chunk fails to load

* test(vscode-ide-companion): gate the transcript blocks wiring into the WebShell renderer

* test(vscode-ide-companion): gate the transcriptUpdate forwarding from agent to webview

* fix(vscode-ide-companion): correct canonical MIT disclaimer wording in notices fallback

* fix(vscode-ide-companion): surface locally generated notices and aborted sends in the transcript UI

* fix(vscode-ide-companion): correlate streamEnd with the active request in the transcript hook

* fix(vscode-ide-companion): pin transcript session guard at clear/load boundaries with the fresh session id

* chore(ci): refresh cua workflow size baseline

* fix(vscode-ide-companion): split file links on raw # before percent-decoding

normalizeExplicitFileLink decoded the whole value before splitting on #, so an encoded %23 in a filename was treated as a fragment delimiter and truncated the path. Split on the raw # first and decode the path and fragment parts separately; the file:// branch decodes only the path component. resolveFileLinkFromAnchor also no longer runs URL decoding/fragment logic over the anchor-text fallback: the text is a literal path, which keeps the /export 'export (#1).html' links (whose file: href the sanitizer strips) clickable.

* fix(vscode-ide-companion): stamp cached history rows as discrete transcript messages

Cached-history seeding emitted each row as a bare *_message_chunk with no promptId/sourceRecordIds/_meta, so the shared reducer merged runs of consecutive same-role cached rows (Tool Result / telemetry / Plan rows per turn) into one plain-concatenated block, and a dropped whitespace-only user row let different turns fuse. Stamp every synthesized cached row with the reducer's existing anti-merge marker (_meta.qwenDiscreteMessage) so offline restores render the same discrete blocks as live replays.

* fix(vscode-ide-companion): resolve tool-group copy rows and copy tool content parts

Copy Message silently failed on every tool row: web-shell keys tool_group rows as msg:tg-<block id>, which findBlockByRowKey never matched. Strip the tg- prefix before the existing exact/longest-prefix matching; merged groups share the first block's key, so a group row resolves to the group's first tool block (documented). The tool case of getBlockCopyText also serialized only title + details (the input summary), dropping the output text and diffs the timeline renders; walk block.content and append text parts and ---/+++ diff renderings like the pre-PR formatToolCallForCopy did, restoring Copy Message / Copy All parity for tool rows.

* fix(vscode-ide-companion): close remaining transcript regressions

* fix(vscode-ide-companion): hide internal image references
2026-08-24 07:41:51 +00:00
4ddbf227e8 feat(mcp): add MCP 2026 core and WebShell Apps host (#8992)
* feat(mcp): add 2026 protocol negotiation

* feat(mcp): render MCP Apps in WebShell

* fix(mcp): keep legacy tool discovery lenient

* fix(mcp): keep Apps HTML out of TUI and honor tool visibility

TUI and history compaction dumped mcp_app HTML as JSON, and discoverTools registered app-only tools for the model.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): stabilize AppBridge lifetime and close sandbox CSP gaps

Theme toggles and transcript reseeds were tearing down MCP Apps; the host CSP also allowed any loopback port and form posts bypassed connect-src.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): list under-declared modern MCP capabilities over the wire

v2 typed helpers return [] without a request when a capability is omitted.
Use them only when the server declared the capability, and keep Apps
unmounted in collapsed tool rows.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): keep Apps sandbox reachable and list past 64 pages

Co-authored-by: Cursor <[email protected]>

* fix(mcp): reject empty compacted html and keep MCP Apps expanded in multi-tool groups

Fixes R3-1 and R3-2 review comments:

R3-1: getMcpAppDisplay now rejects empty html strings (from compaction)
so session replay shows fallbackText instead of mounting an empty iframe.

R3-2: ToolGroup now checks for MCP apps across all tools (not just
singleTool), auto-expands when any tool has an MCP app, and keeps
MCP app rows expanded (summaryOnly=false, forceExpanded=true) even
when adjacent tool calls are merged into the group.

* feat(web-shell): fold thinking into the compact-mode tool summary (#9148)

Compact mode used to drop thinking messages entirely, so a running turn
gave no indication of the thinking step. Keep the thoughts and aggregate
them with the adjacent tools into one summary: a streaming thought reads
"Thinking…" with the running shimmer, and a completed thought settles into
a click-to-expand row in its original interleaved position. The translate
action is preserved on both the thinking block and the folded thought
rows, and the merged group gets a synthetic id so its expanded state never
leaks into non-compact mode.

Co-authored-by: 钉萁 <[email protected]>

* fix(mcp): address app discovery and sandbox regressions

* fix(web-shell): keep MCP apps expanded in compact summaries

* Revert "feat(web-shell): fold thinking into the compact-mode tool summary (#9148)"

This reverts commit ab2eebc5d3.

* fix(web-shell): render compacted MCP App fallback and teardown before unload

Compacted history keeps type:mcp_app with empty html; show fallbackText instead of a blank sandbox, and wait for ui/resource-teardown before unloading the iframe.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): bound the discover probe and raise the daemon bundle cap

Silent legacy stdio servers inherited the 10-minute request timeout for server/discover. Cap the probe at 5s so fallback fits the discovery window, and raise the browser bundle budget after the main merge overflowed CI by 47 bytes.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): skip version-negotiation probe on remote transports

SDK v2 rejects HTTP server/discover timeouts without falling back to initialize, and the 5s probe consumed the entire remote discovery window.

Co-authored-by: Cursor <[email protected]>

* fix(web-shell): keep MCP App iframe src across deferred teardown

Deferred unload() was clearing src on the live iframe after a remount, so the new AppBridge never saw sandbox-proxy-ready.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): honor listing-level MCP App CSP and permissions

registerAppResource puts ui.csp/permissions on resources/list, and resources/read does not merge that metadata into content entries.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): reuse session client for list and emit app fallback text

Co-authored-by: Cursor <[email protected]>

* fix(mcp): keep mcp list and IPv6 sandbox CSP valid

Give qwen mcp list leftover handshake budget after the 5s discover probe, and stop emitting invalid [::1] CSP origins.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): keep modern list and short discovery budgets working

Drop the era-illegal ping after mcp list connect, shrink the stdio discover probe to the discovery window, and document that remotes stay on legacy initialize until the SDK can fall back.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): keep the 2026 slice free of review-only extras

Drop the global tools/list page cap, generated companion notices, and the review screenshot so this PR stays on stdio 2026 plus the WebShell Apps host.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): restore generated companion notices after the SDK v2 bump

CI regenerates NOTICES.txt from the lockfile; the file has to ship with the new MCP client dependencies.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): isolate the Apps proxy from WebShell storage

Drop allow-same-origin on the outer sandbox iframe so a default localhost daemon cannot read the WebShell session token.

Co-authored-by: Cursor <[email protected]>

* fix(mcp): harden fallback and app sandbox

* fix(core): preserve large and app-only MCP catalogs

* fix(mcp): preserve legacy negotiation compatibility

* fix(mcp): default stdio negotiation to legacy

---------

Co-authored-by: Cursor <[email protected]>
Co-authored-by: ytahdn <[email protected]>
Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: YungSen Hsin <[email protected]>
2026-08-23 18:34:30 +00:00
ytahdnand钉萁 1e921ce323 fix(web-shell): isolate component styles from host CSS (#7466)
Co-authored-by: 钉萁 <[email protected]>
2026-07-22 02:21:17 +00:00
Changxiao Ruanandqwen-code-dev-bot 9b6da8e604 feat(webshell): replay ChatRecord history in readonly WebShell (#6999)
* feat(web-shell): add readonly transcript renderer

* feat(transcript): project chat records to daemon transcript

* chore: remove unrelated merge changes

* fix(cli): align transcript replay test mock

* fix(transcript): preserve replay metadata and todo context

Keep vision disclosures and assistant usage visible across tool replay boundaries.

Propagate plan tool-call identity through daemon projection and provide Todo contexts in WebShell so snapshots remain independent. Accept session_source records and cover the cross-layer behavior with regression tests.

* docs(transcript): translate designs and remove benchmark table

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-07-19 00:09:44 +00:00