Commit Graph
98 Commits
Author SHA1 Message Date
Bob.qwencodeandgithub-actions[bot] 208beb2b3b chore(release): v0.26.9 [skip ci]
* chore(release): v0.26.9

* docs(changelog): sync for v0.26.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-06 18:03:10 +00:00
jinyeandQwen-Coder 32d22f950c feat(managed-agent): support durable structured user questions (#16433)
Connect Hosted question actions, structured answers, durable continuation, and the WebShell question form. Cover canonical response replay and pending-question recovery across Harness and Java context restarts.

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 09:23:44 +00:00
Bob.qwencodeandgithub-actions[bot] 3718b57fa8 chore(release): v0.26.8 (#16409)
* chore(release): v0.26.8

* docs(changelog): sync for v0.26.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-06 01:02:08 +08:00
Shaojin Wenandverify-round e2d3243863 fix(web-shell): express the PR watch events dependency in the channel editor (#16236)
* fix(web-shell): express the PR watch events dependency in the channel editor

The GitHub adapter rejects a non-empty prWatchEvents unless
deliverToBoundSessions is enabled, but the editor never expressed that
dependency: operators could tick watch events, press Save, and receive a
raw English daemon refusal that read as "the selection cannot be saved".

Group the bound-delivery switch and the watch-event checkboxes into one
framed panel, disable the checkboxes with an inline hint until the switch
is on, and mirror the runtime rule as a client-side validation backstop
for hand-written stored configs that carry events without delivery.

* fix(web-shell): keep stored watch selections clearable while delivery is off

Review follow-ups on the bound-session panel:

- A stored selection stays uncheckable-while-gated no more: with delivery
  off, selected events (including unknown stored values) can be cleared
  directly, while new selections remain disabled, so turning delivery off
  never strands the operator between a dependency error and disabled boxes.
- Turning the delivery switch on clears the stale requiresBoundDelivery
  error instead of leaving the instruction under the now-on switch.
- The disabled hint moves into FieldShell's existing hint slot, restoring
  the "description is the first paragraph" invariant; the hint gets an id,
  aria-describedby on the checkbox group and a persistent polite live
  region so screen readers hear why the boxes are gated.
- The render gate now carries the same github scope as the validation
  mirror, and checked-but-disabled rows keep the selected border.
- Tests: shutdown path, stale-error retirement, aria association, and a
  stored delivery-on mount; visuals scenario seeds the bound-session
  fields and captures the gated and enabled states.

* fix(web-shell): scope dependency-error retirement to the dependency message

The switch-on clear deleted the whole prWatchEvents error slot, wiping the
higher-precedence invalidOption message while an unknown stored value was
still ticked; and unticking the last selected event — the other remedy —
left the dependency alert over an empty, valid selection. Retire only the
dependency message, from both remedies, and pin the live region's
persistence (empty region mounted before the hint, same node across the
transition) plus the hint-less rendering of the pre-existing hint slots.

* fix(web-shell): retire watch-events errors by re-deriving validity

Round-4 review follow-ups, all in this PR:

- R3-1: replace the string-matched dependency clear with a per-field
  re-validation on edit, so unticking an unknown stored value retires the
  invalidOption instruction it satisfies (and a remaining valid event swaps
  the slot to the still-unsatisfied dependency message).
- R3-3: submit now scrolls the first erroring field into view even when it
  is not an advanced field; a stored watch-events refusal below the fold is
  no longer invisible.
- R3-2: pin the empty-panel guard with EN/zh absence assertions on a
  non-GitHub descriptor.
- R3-4: table-driven parity test feeds the plugin's own descriptor and
  config through both the editor mirror and the daemon validator
  (test-only devDependency, never bundled); a lone change on either side
  now fails the table.

Each fix carries a mutation witness; 286 channel tests green.

* fix(web-shell): re-derive only shown errors, in both switch directions

Round-5 review follow-ups, all in this PR:

- R4-1: refreshErrors re-derives only the error keys already on screen, so
  a remedy retires exactly the instruction it satisfies without surfacing
  validation the operator never submitted (submit stays the only population
  site), and the delivery switch re-derives in both directions so a shown
  instruction is never silently dropped. New tests pin pre-submit silence
  and shown-message survival across switch toggles.
- R4-2: narrow plugin.management once at module scope in the parity test so
  the optional contract member stays TS18048-clean if test files ever join
  the package typecheck, with a clear error instead of a load-time TypeError.
- Deferred note from the same round: pin the data-disabled styling hook on
  gated rows (dimmed) versus clearable stored rows (selected styling).

Each fix carries a mutation witness; 288 channel tests green.

---------

Co-authored-by: verify-round <verify-round@local>
2026-11-04 14:42:07 +00:00
Bob.qwencodeandgithub-actions[bot] 7d5a82443a chore(release): v0.26.7 [skip ci]
* chore(release): v0.26.7

* docs(changelog): sync for v0.26.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-04 04:49:30 +00:00
bc19325983 feat(web-shell): add host mount entries and align managed empty-state layout (#15928)
* feat(web-shell): 增加 managed-host 构建入口与托管会话页

为产品侧集成提供独立的 managed-host 产物(--mode managed-host)与托管会话页:
不挂 daemon workspace/session context,托管会话由 provider 直接渲染列表、会话与输入框。

包含:vite lib 构建模式、managed-host 渲染入口及测试、ManagedSessionsPage 托管分支与
测试、provider 类型调整、README 与中英文设计说明。本次不涉及宿主注入能力。

* feat(web-shell): 托管宿主可注入侧栏导航 props

ManagedAgentWebShell 增加可选导航 props 并原样透传给 ManagedSessionsPage:
navigationTarget / archivedNavigationTarget / onArchivedSessionsStatusChange /
navigationActive / navigationSearch。

宿主据此决定是否把官方会话列表 portal 到自己的侧栏(navigationTarget 为 null 时
仍走 home 布局但不 portal),并同步搜索词与归档计数。类型复用 ManagedSessionsPage
现有 props,不新增重复定义;新增 2 个用例覆盖原样透传与未传时保持 undefined。

* feat(web-shell): add full-page app-host entry and align managed empty-state layout

- Add app-host mount entry (renderWebShellApp) so a non-React host can render the
  complete WebShell (sidebar + welcome + composer) by injecting baseUrl/provider;
  no shell is hand-drawn and no backend address is hardcoded.
- Center the managed empty state like the standalone chat (content shrinks so the
  chatPane centers it) instead of pinning the welcome to the top and composer to
  the bottom; gate the welcome header off when a failed session is selected.
- Surface session failure and unconfirmed-submission notices together above the
  composer, dedupe the same message across channels, and style them as callouts.
- A pending prompt owned by another session now offers a redirect to the owning
  conversation instead of retry/discard from a foreign session; reconcile tests.
- Build app-host with global (unscoped) CSS so root-level state classes match.

* test(web-shell): align managed status smoke assertions

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

* test(web-shell): reach the owning conversation before retrying a foreign pending

A pending prompt owned by another conversation no longer offers retry in the
foreign session: it renders "Open the original conversation" instead. The
spec still clicked "Retry the same request" straight from the foreign session,
so the two foreign variants timed out waiting for a button that is deliberately
absent from this head while the owner variant passed.

Follow the redirect first, then retry. Owner variant unchanged.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvwo8rnm58

* docs(web-shell): document the app-host entry and drop ignored e2e links

The review on head 932a61d8dc read `app-host` as undocumented: no README
section, no mention in either design doc. Document it the same way
`managed-host` is documented -- entry API, build mode, served directory --
and record the unscoped-CSS behaviour as a deliberate decision narrowed to
that bundle, naming the entries that keep scoped CSS. Also state that neither
host mode is part of `npm run build`, which is what the wiring actually does.

Both design docs linked `.qwen/e2e-tests/managed-host-webshell.md`, which
`.gitignore` excludes (`.qwen/*`), so the links are dropped and the
statement they supported stays.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvxdyp0v69

* fix(web-shell): revert breaking changes to existing consumers

- Revert `isChatEmptyState` to `!managedMode &&` (was: `!connection.sessionId && !(managedAgentProvider !== undefined && managedSessionId)`), restoring the original behavior where managed mode is never empty state. The head condition broke existing managed consumers by making them hit empty-state layout when no session was selected.

- Revert `sidebarRailEnabled` to drop `managedAgentProvider === undefined &&`, restoring rail for existing managed consumers. The condition was app-host-specific and should not affect hosts that already pass a provider.

- Revert `ManagedSessionsPage` inline `flex: 0 0 auto` (was unconditional in empty state), restoring `flex: 1 1 auto` for the managed entry. The shrink was only correct when paired with `.appChatEmpty .chatPane { justify-content: center }`, which only exists in the full-page `renderWebShellApp` entry. In `renderManagedAgentWebShell` the class is absent, so the shrink alone produced a 136px content block pinned to the top with 550px dead space below.

- Remove `skipScope` from `vite.lib.config.ts`: the justification ("scoped selectors use [data-web-shell-root] as an ancestor anchor") is factually incorrect. `COMPONENT_SCOPE` includes `:where([data-web-shell-root][data-web-shell-shadcn], ...)` which matches the element itself, so scoped bundles already apply state classes on the root element. Keep scoping for all modes.

- Update README and both design docs to reflect the corrected scoping behavior.

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

* chore(web-shell): remove app-host entry

The app-host entry (renderWebShellApp) had no build wiring (absent from
build script, exports, files, and no CI job invoked --mode app-host), no
test coverage, and hardcoded a specific host's sidebar/footer configuration.
Remove it from this PR; it can be reintroduced in a follow-up with proper
packaging and tests once a named consumer exists.

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

* fix(web-shell): drop unused mode parameter from injectCssModules

The mode parameter became unused after the skipScope removal. Remove it to
fix the ESLint no-unused-vars error.

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

* docs(web-shell): scope the managed-host entry and correct stale layout notes

- README: say plainly that `managed-host` is source-only (no `build`,
  `exports` or `files` wiring, so it is not in the npm package), and record
  that one shell per shadow root is the contract.
- package.json: keep the orphan `dist/types/managed-host.d.ts` out of the
  published tree so the package does not ship a type with no implementation.
- ManagedSessionsPage: point the `summary.failure` dedupe comment at the
  current failure block above the composer, and record why a session switch
  resets failure and discard notices.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvy1jgjo76

* docs(web-shell): state the real consequence of the session-switch reset

The comment claimed the list error is re-derived on a session switch. It is
not: the list effect's deps exclude sessionId, so a catalog-load error is
hidden until provider/listRevision changes. Say what actually happens.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvy1jgjo76

* fix(web-shell): keep managed sessions visible when workspaces collapse

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

* fix(web-shell): address doudouOUC review feedback

- Remove shadow-DOM style adoption from renderManagedAgentWebShell: the
  shared CSSStyleSheet cache caused cross-contamination when two shells
  shared one shadow root. Hosts using shadow DOM should adopt styles
  themselves or use a plain container.
- Scope the error/discard reset effect to hostSessionOnly only: the
  unconditional clear on [provider, sessionId] hid the catalog-load error
  for existing managed consumers on every session switch without retrying.
- Update the managed-host test to match the removed shadow-DOM behavior.
- Drop the stale shadow-root note from README.

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

* fix(web-shell): preserve styles for managed hosts in shared shadow roots

Restore the documented managed-host ShadowRoot style lifecycle and keep the shared package sheet until its last owner unmounts. Cover both fallback and constructable sheet behavior.

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

* fix(web-shell): center the managed host empty conversation as one group

The host mount entry has no .appChatEmpty ancestor, so the managed empty conversation kept its full-height content box and pinned the composer to the bottom. Give host-mounted empty conversations the same shrink-to-content centering, mount the entry through the managedHome fill wrapper, and drop the empty trailing status row below the composer.

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

* fix(web-shell): let a host-mode session clear through without an echo channel

hostSessionOnly alone marks the shell controlled, so the page's clear
signal after deleting or archiving the current session was dropped when
the host passed no onSessionChange (the README-documented mount). The
deleted conversation stayed on screen and sendable.

Allow the internal write for a clear (undefined) when no echo channel
exists; new selections stay host-owned, preserving the existing
'unrequested selection is suppressed' contract. The bot's suggested
controlled-expression rewrite would have broken that contract, so the
gate lives in the handler instead.

Witness: the new no-echo test fails at 5683eed0 without this change
(sessionId stays 's1') and passes with it; the echo-channel test pins
that the fix does not leak internal writes into echo-owned hosts.

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-03 16:50:24 +08:00
Bob.qwencodeandgithub-actions[bot] 5e267805c3 chore(release): v0.26.6 (#16163)
* chore(release): v0.26.6

* docs(changelog): sync for v0.26.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-11-03 07:00:07 +08:00
e26f377a28 chore(release): v0.26.5 (#16035)
* chore(release): v0.26.5

* docs(changelog): sync for v0.26.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-01 12:40:25 +08:00
Bob.qwencodeandgithub-actions[bot] ae51b081ce chore(release): v0.26.4 [skip ci]
* chore(release): v0.26.4

* docs(changelog): sync for v0.26.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-29 15:14:52 +00:00
Bob.qwencodeandgithub-actions[bot] 08d97f383a chore(release): sdk-typescript v0.1.25 [skip ci]
* chore(release): v0.26.3

* chore(release): sdk-typescript v0.1.25

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-28 17:02:53 +00:00
Bob.qwencodeandgithub-actions[bot] 5180685d97 chore(release): sdk-typescript v0.1.24 [skip ci]
* chore(release): v0.26.2

* chore(release): sdk-typescript v0.1.24

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-27 18:01:25 +00:00
aa6b65a52a chore(release): v0.26.1 (#15468)
* chore(release): v0.26.1

* docs(changelog): sync for v0.26.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 易良 <[email protected]>
2026-10-25 01:27:17 +08:00
Bob.qwencodeandgithub-actions[bot] 8f259db527 chore(release): v0.26.0 [skip ci]
* chore(release): v0.26.0

* docs(changelog): sync for v0.26.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-22 02:08:04 +00:00
ytahdnand钉萁 b08e41b25e feat(web-shell): preview DOCX files across file entry points (#14868)
Co-authored-by: 钉萁 <[email protected]>
2026-10-20 22:40:46 +08:00
Bob.qwencodeandgithub-actions[bot] 530a22680d chore(release): v0.25.3 [skip ci]
* chore(release): v0.25.3

* docs(changelog): sync for v0.25.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-19 07:38:12 +00:00
a8eb74fdf5 fix(web-shell): polish files and trim preview resources (#14143)
* fix(web-shell): polish files and trim preview resources

* test(cli): observe pending child Hook promises

* fix(web-shell): address Files browser review findings

* fix(vscode): sync Pierre modification notices

* chore: ignore the repo-local .pnpm-store fallback directory

pnpm falls back to a project-local store at .pnpm-store/ when the global
store directory is not writable (e.g. sandboxed CI runners). It is a
regenerable cache that node_modules hard-links into; its thousands of
untracked files otherwise dirty the working tree and fail deterministic
workspace-clean verification.

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

* fix(web-shell): address Pierre preview and file display review findings

- getPreviewLanguage folds case on the extension only, so README.MD and
  config.YML highlight while whole-name detector keys (Gemfile, BUILD)
  keep their case-sensitive behavior
- formatFileName strips the full Default_Ignorable_Code_Point class so
  zero-advance Hangul fillers and variation selectors cannot desync the
  tree search projection from the rendered name
- pin the previously unwitnessed pierre-build guards: redirected Shiki
  subpaths count toward the 4 MiB budget, getModuleInfo may return null
  for redirected ids, emitted assets are skipped, and entry-module
  resolveId calls carry no importer
- pin the theme name->module mapping with exact token colors and give
  the formatFileName display sites discriminating coverage with
  repeated-space names

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: qwen-code[bot] <qwen-code[bot]@users.noreply.github.com>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-10-16 16:45:56 +08:00
Bob.qwencodeandgithub-actions[bot] 25ef506c03 chore(release): sdk-typescript v0.1.20 [skip ci]
* chore(release): v0.25.2

* chore(release): sdk-typescript v0.1.20

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-15 17:01:33 +00:00
ytahdnand钉萁 df5470e430 feat(web-shell): Add workspace file browser and previews (#13878)
* feat(web-shell): Add workspace file browser and previews

* fix(web-shell): Restore source preview CI fixtures and notices

* fix(web-shell): Address file pane review blockers

* fix(web-shell): Resolve review regressions and strengthen file checks

---------

Co-authored-by: 钉萁 <[email protected]>
2026-10-13 09:38:47 +00:00
ef38ad7753 feat(web-shell): add read-only Excel artifact previews (#13664)
* feat(web-shell): add read-only Excel artifact previews

* fix(vscode): refresh notices for Excel preview dependencies

* fix(web-shell): bound Excel merge loading and preserve preview content

* fix(web-shell): bound Excel preview work and preserve recovery

* fix(web-shell): harden Excel preview values and recovery

* fix(web-shell): include bundled ExcelJS dependency licenses

* fix(web-shell): guard column expansion and complete ExcelJS notices

* fix(web-shell): complete Excel color fallback and review guards

* test(web-shell): follow artifact filename labels in Excel previews

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-10-13 02:43:10 +00:00
ytahdnand钉萁 f3f5e005a3 feat(web-shell): support copying selected reply content (#13833)
* feat(web-shell): support copying selected reply content

* fix(web-shell): preserve selected reply data and menu lifecycle

* fix(web-shell): keep selection copying faithful to visible content

---------

Co-authored-by: 钉萁 <[email protected]>
2026-10-12 09:43:56 +00:00
Bob.qwencodeandgithub-actions[bot] 726e8d77f7 chore(release): v0.25.1 (#13904)
* chore(release): v0.25.1

* docs(changelog): sync for v0.25.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-11 12:00:47 +00:00
易良andQwen-Coder c468f2ab66 fix(web-shell): allow deleting queued prompts during admission (#13594)
* fix(web-shell): allow deleting queued prompts during admission

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

* fix(web-shell): keep aborted text-only prompts on the admission path

The cancelled-upload cleanup ran an unconditional throwIfAborted after
attachment handling, so a text-only submission whose signal was already
aborted rejected instead of reaching admission. Those submissions must
still be admitted so the accepted prompt id can be removed exactly, which
is the contract DaemonSessionProvider covers. Scope the early abort to
submissions that actually uploaded attachments.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmuxzdx6dcb

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-07 16:40:26 +00:00
Bob.qwencodeandgithub-actions[bot] 8d864a9f21 chore(release): v0.25.0 (#13448)
* chore(release): v0.25.0

* docs(changelog): sync for v0.25.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-10-05 10:07:16 +00:00
Bob.qwencodeandgithub-actions[bot] 34dbaf6741 chore(release): v0.24.7 (#13025)
* chore(release): v0.24.7

* docs(changelog): sync for v0.24.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-29 14:36:56 +00:00
Shaojin Wen 692a5d7446 feat(managed-agent): Add the public API contract and contract tests (#12808)
* feat(managed-agent): Add the public API contract and contract tests

Move the reviewed Managed Agent OpenAPI into the server module as the
single source for the public and WebShell routes (v1.13), and check the
server and the WebShell client against it.

- A contract test compares the mapped Spring routes, the API records,
  and the real requests, responses, declared headers and SSE frames of
  every partial route with the spec. Current differences are recorded
  as expected failures that the later Stage D slices remove; a fixed
  gap that stays listed fails as well.
- The WebShell client types are generated from the spec without its
  planned surface, and a test fails when the committed types are stale.
- Rename, unarchive, archive and delete, which the server already
  serves, are recorded as partial routes.

Part of #12793.

* test(managed-agent): Cover archived reads and WebShell first input in the contract

Address review feedback on the contract test and its callers.

- The real-traffic scenario now reads an archived Session through get
  and list, and creates a WebShell Session with its first message. That
  records three more gaps: archived Sessions read back with status
  "archived", which the contract's enum lacks, and the WebShell create
  request carries the same "text" input type as the other D2 lines.
- A property that appears in several oneOf or allOf members counts as
  planned only when every member marks it planned.
- Replace the deprecated JsonNode.fields() calls.
- The managed-progress e2e spec builds a Session with agentId and
  events with data, as the generated types require.
- The design note records the gaps that the gap file cannot hold:
  undeclared error responses, the full-page has_more semantics,
  workspace-bound Sessions, and the merge order with W0d.

* test(managed-agent): Reject unknown gap categories and quiet the validator

Address the sandboxed verification of the contract test.

- Every line of the known-gaps file must start with a category that a
  test filters on. A line with any other prefix was never compared, so
  it could not be reported as resolved; it now fails every contract
  test and names the line.
- The validator treats keywords outside JSON Schema as annotations
  without logging, which removes the nine "Unknown keyword" warnings
  that loading the whole OpenAPI document produced on every run.
- The design note no longer says that no route changes status: archive
  and delete move from planned to partial, and none becomes implemented.

* docs(managed-agent): State exactly what openapi-typescript reads from supports-color

The override comment and the design note said the generator reads only
stdout.hasBasic, which 7.2.0 provides. It reads stdout and its hasBasic
flag; 7.2.0 exports the same stdout, false without a TTY, and the
truthiness check on stdout is what keeps colours off there.
2026-09-27 04:08:41 +00:00
Bob.qwencodeandgithub-actions[bot] 16496a71ec chore(release): v0.24.6 (#12722)
* chore(release): v0.24.6

* docs(changelog): sync for v0.24.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-26 01:06:56 +00:00
Bob.qwencodeandgithub-actions[bot] af5e90bfb2 chore(release): v0.24.5 (#12661)
* chore(release): v0.24.5

* docs(changelog): sync for v0.24.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-24 17:19:31 +00:00
dreamWB 1d30ddc9fa feat(web-shell): support selective host artifact integration (#12588)
* feat(web-shell): support selective host artifact handling and shared highlighting

* fix(web-shell): preserve legacy asynchronous panel handlers

* fix(web-shell): preserve native artifact downloads in embedded hosts

* test(web-shell): keep download timing checks in the shared helper test

* test(web-shell): focus host integration regression coverage

* docs(web-shell): keep host integration design independent of run status
2026-09-24 09:14:37 +00:00
Bob.qwencodeandgithub-actions[bot] 1b26d38b5c chore(release): v0.24.4 (#12476)
* chore(release): v0.24.4

* docs(changelog): sync for v0.24.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-22 15:19:32 +00:00
Bob.qwencodeandgithub-actions[bot] 364032a032 chore(release): v0.24.3 (#12418)
* chore(release): v0.24.3

* docs(changelog): sync for v0.24.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-21 14:46:57 +00:00
Bob.qwencodeandgithub-actions[bot] eceaede18e chore(release): v0.24.2 (#12359)
* chore(release): v0.24.2

* docs(changelog): sync for v0.24.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-20 16:05:04 +00:00
易良 9e6d058b41 fix(web-shell): make the published package resolvable for consumers (#12188)
* fix(web-shell): make the published package resolvable for consumers

Three packaging defects were invisible until after `npm publish`, where a
version can no longer be corrected in place:

- Three UI components imported sibling primitives through the repo-only `@/`
  alias, and `tsc` copied that alias verbatim into the emitted declarations, so
  consumers could not resolve the field, alert-dialog and toggle-group types.
  Those imports are now relative and the declaration tree carries no `@/`.
- `fzf`, `@tanstack/react-virtual` and `@xterm/addon-fit` were declared as
  runtime dependencies although the library bundles inline them and no emitted
  declaration mentions them; they move to devDependencies. The packages the
  declarations do reference — `@xterm/xterm`, `@tanstack/react-table` and
  `@modelcontextprotocol/ext-apps` — stay declared.
- A failed type step leaves the shared dist without declarations while the
  exports map still announces them. A `prepublishOnly` hook now refuses to pack
  when an exports target is missing or a declaration imports through `@/`.

* fix(web-shell): keep bundled deps declared for the extension notices

Demoting `fzf`, `@tanstack/react-virtual` and `@xterm/addon-fit` to
`devDependencies` expires the companion's NOTICES.txt freshness check: that
file is generated from the companion's production dependency graph, and the
extension bundles this package's inlined code while shipping the notices in
the VSIX. Both `dist/extension.cjs` and `dist/webview.js` contain that code,
so regenerating would delete 79 lines of MIT notices for software that still
reaches users.

The three packages therefore stay in `dependencies`, and the lockfiles match
the base commit again. `npm run generate:notices` is a no-op at 659
dependencies.

* fix(web-shell): guard shared chunks and drop the last alias sibling import

`dialog.tsx` was the only component still importing a sibling primitive
through the repo's `@/` alias. Its button import is elided from the emitted
declarations today, so nothing leaks yet, but it is the same alias in the same
position and would leak the moment the button reaches a public type. All four
sibling imports are now relative.

The publish guard checked that `exports` targets exist on disk, which says
nothing about the chunks the bundles import by relative path: `dist/index.js`
and `dist/daemon-react-sdk.js` both pull in a hash-named chunk at the `dist`
root, published only because the file list globs `dist/*.js`. A chunk emitted
into a subdirectory would be announced by an entry point but never packed, so
the guard now refuses to pack when an entry's relative import was not built.
2026-09-19 09:57:54 +00:00
Bob.qwencodeandgithub-actions[bot] 94946f1bf8 chore(release): v0.24.1 (#12239)
* chore(release): v0.24.1

* docs(changelog): sync for v0.24.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-19 08:23:48 +00:00
易良andQwen-Coder e596a410bb feat(web-shell): publish the package with the cli release (#12178)
* feat(web-shell): publish the package with the cli release

The release workflow published the cli and the channel packages but never
@qwen-code/web-shell, so the npm artifact only moved when someone published
it by hand.

Add the package to the release step and record its repository so npm accepts
the provenance attestation the workflow requests.

* fix(release): probe @qwen-code/web-shell in the push-time version guard

publish_package 'packages/web-shell' made the package CI-published, but
PUBLISHED_PACKAGES — the sole input to assertVersionUnreleased — still
listed only twelve names. A release shipped only as @qwen-code/web-shell
therefore read as unshipped: the version-collision bump never fired and
the force-push guard could proceed over an already-published version.

Widen the guard list, its pin in get-release-version.test.js, and
generalise "keeps the publish allowlist and the guard package set in
step" so every bare publish_package '<dir>' target is resolved through
the package.json it publishes. The channel-loop comparison alone could
not see this addition, which is how it shipped unguarded.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmu6tb0j73c

* test(release): simplify publish guard coverage

* fix(release): publish web-shell after the channels

Review on #12178 (R1-3): the step runs under `set -eo pipefail` and every
publish_package call is bare, so inserting the newest name ahead of the
channel packages makes a failure of its first CI publish strand all of
them. Publish it last instead.

Also append the new entry to PUBLISHED_PACKAGES rather than inserting it
after audio-capture: the version-guard tests read that array by index, so
its position is load-bearing. Record that in the pinned assertion.

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-18 12:17:07 +00:00
Bob.qwencodeandgithub-actions[bot] c53287da5f chore(release): v0.24.0 (#12031)
* chore(release): v0.24.0

* docs(changelog): sync for v0.24.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-16 13:31:06 +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
Bob.qwencodeandgithub-actions[bot] af4da34719 chore(release): v0.23.4 (#11880)
* chore(release): v0.23.4

* docs(changelog): sync for v0.23.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-14 15:59:33 +00:00
易良andShaojin Wen 8a21551599 chore: enforce Playwright version parity and close the deferred #11101 follow-ups (#11371)
* chore: enforce Playwright parity and close deferred #11101 follow-ups

Works the four items tracked in #11336:

- Pin Web Shell's @playwright/test to the same exact version as the root
  playwright dependency, and fail `npm run check:lockfile` when the two
  direct manifests drift apart or their resolved root trees stop matching
  the pin. The check deliberately tolerates the packages/mobile-mcp tree:
  mobilewright depends on playwright and playwright-core at an exact
  older revision, so no manifest edit in this repository can dedupe it.
- Correct the "Install evidence browser" rationale in qwen-triage.yml and
  its restatement in the workflow test. The two-trees premise no longer
  holds now that both manifests are pinned; the resolution behavior is
  unchanged, since resolving from the harness's own directory still keeps
  the binary tied to the package it imports.
- Cover the ENOENT fallback of the exported isMainModule helper. The
  existing runner.py assertion takes the realpath success path because
  that file exists, so the fallback stayed unpinned.
- Separate the CLI-only from the daemon-only followup suggestion gates in
  the documentation instead of pointing daemon clients at the blended
  CLI-oriented list, keeping the headless/SDK opt-out guidance.

* chore: pin the Playwright parity check and correct its scope notes

Follow-up to the round-1 review on this PR.

The parity block had no test in either direction: the only suite that runs the
script asserted the two pre-existing banners, so deleting the block, inverting a
comparison, or breaking the importer derivation left both `npm run test:scripts`
and CI's `check:lockfile` gate green. That is the defect class this PR exists to
remove — the same diff rewrites two rationale comments to lean on the check, so
an unpinned guard rots into a comment that lies. Add the third banner to the
existing suite and a fixture suite that runs the script against perturbed copies
of the real manifests and lockfiles, covering both drift arms and the two shapes
that must stay green.

Also correct three properties of the block the review found:

- The inventory comment claimed two Playwright declarations sit outside the
  invariant; `packages/mobile-mcp` declares `@playwright/test` as a range
  directly, in a workspace member, and was not named. It is the only in-workspace
  range on a name this check pins, so it is what the resolved-version message
  should point an operator at.
- The agreement check destructured exactly two entries while the comment invited
  maintainers to extend the list, so a third entry would have received the
  exactness check and silently skipped the comparison the block exists for.
  Compare every entry against the first and derive the resolved-tree locations
  from the same list instead of repeating them as a second literal.
- The pnpm arm prescribed "regenerate it" for every divergence, but
  `pnpm-workspace.yaml`'s `overrides:` and `.pnpmfile.mjs` both rewrite the
  recorded specifier, so regeneration is a no-op for those. Name the deciding
  layer in the messages and the importer list's source for a missing entry.

* docs: stop counting the followup suggestion code paths

Merging main brought in the OpenTUI port of ink's AppContainer effect
(packages/cli/src/ui/opentui/followup-generation.ts), which is a third
non-test `generatePromptSuggestion` call site. The section opener claimed
"Two code paths decide this", which a grep now contradicts even though the
substance still holds: the OpenTUI hook applies the same CLI gate set as
AppContainer — interactive, not SDK mode, the Responding to Idle edge, no
error in the last item, no waiting calls, approval mode not plan — and
collapses ink's five explicit dialog checks into `waitingCalls.length === 0`
because the composer is unmounted while a shell dialog is open.

Name the two decision-makers instead of counting implementations, so the
sentence stays true when a renderer is added or retired. The CLI/daemon
split below is unchanged and was re-verified against the merged tree: the
ink gate is byte-identical at AppContainer.tsx:3612-3627, the daemon gate
moved only by line number (Session.ts:5080-5095), and the shared
MIN_ASSISTANT_TURNS gate is still at suggestionGenerator.ts:83,103.

---------

Co-authored-by: Shaojin Wen <[email protected]>
2026-09-12 11:40:46 +00:00
Bob.qwencodeandgithub-actions[bot] 38513b591b chore(release): v0.23.3 (#11583)
* chore(release): v0.23.3

* docs(changelog): sync for v0.23.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-10 15:03:19 +00:00
Bob.qwencodeandgithub-actions[bot] bae90d7afa chore(release): v0.23.2 (#11469)
* chore(release): v0.23.2

* docs(changelog): sync for v0.23.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-09 11:38:23 +00:00
Bob.qwencodeandgithub-actions[bot] c26f6d299f chore(release): v0.23.1 (#11400)
* chore(release): v0.23.1

* docs(changelog): sync for v0.23.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-08 17:01:17 +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
Bob.qwencodeandgithub-actions[bot] 9ffada4eac chore(release): v0.23.0 (#10914)
* chore(release): v0.23.0

* docs(changelog): sync for v0.23.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-09-03 12:52:13 +00:00
Shaojin Wen a8248eaed3 ci: collect coverage only on the post-merge run and run cli tests under node (#10890)
Pull-request runs collected v8 coverage for cli, core, web-shell and
vscode-ide-companion on every push, uploaded it, and fed it to a coverage
comment that failed about as often as it posted; nothing else read the
reports. Measured on the cli suite, instrumentation plus the per-file merge
cost about a fifth of the wall time. Coverage is now switched on by
QWEN_CI_COVERAGE, which the unit-test step sets only for push (main); the
upload gate follows the switch, and the comment job and its composite action
go. The two workspaces that forced --coverage from test:ci rely on the same
switch.

The cli package also ran every test file under jsdom although only 90 of the
1002 touch a document. The default is now node; those 90 files carry a
`@vitest-environment jsdom` control comment. Together the two changes take
the cli suite from 410 s to 290 s on an idle 6-worker host.
2026-09-03 08:49:43 +00:00
fe34a5cf22 refactor(vscode-ide-companion): complete the WebShell UI cutover (#9811)
* 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

* docs(vscode): plan complete Web Shell cutover

* refactor(web-shell): own daemon React bindings

* fix(webui): preserve package entry filenames

* refactor(vscode): complete WebShell UI cutover

* chore(vscode): refresh third-party notices

* fix(vscode): fill embedded chat viewport

* test(web-shell): disambiguate workspace visual locator

* fix(vscode): match embedded chat layout to host

* fix(vscode): compact embedded chat styling

* fix(vscode): align embedded chat density with VS Code

* fix(vscode): complete embedded composer integration

* fix(vscode): restore user message editing after cutover

* fix(vscode): complete WebShell feature parity

* test(vscode-ide-companion): repair host-wiring tests for the WebShell cutover

* refactor(vscode-ide-companion): replace webui build scanner with an ESLint boundary rule

The bespoke recursive source scanner reimplemented a dependency-boundary
check on every extension build. A scoped no-restricted-imports rule
enforces the same boundary on every lint run with less custom code; the
manifest dependency entry was already removed by the cutover.

* fix(web-shell): keep ChatEditor commands prop referentially stable (#9811)

The `additionalSlashCommands = []` destructure default allocated a fresh
array on every App render, invalidating the `commands` useMemo and breaking
ChatEditor memoization on every transcript-only re-render. Default to a
module-level constant instead, matching the existing EMPTY_* convention.

Also align the /skills completion expectation with the autoSubmit field the
completion source intentionally emits for leaf skill items.

* fix(vscode): distinguish the VS Code channel and localize its chrome

The companion now drives Web Shell against a shared `qwen serve` daemon,
so the CLI, the browser Web Shell, and this extension all create sessions
in the same workspace catalog. Web Shell recorded `'default'` for every
surface, leaving VS Code conversations indistinguishable from terminal and
browser ones — the panel's history listed sessions the user never opened
here, and nothing attributed a session back to the editor.

Give Web Shell a `sessionSourceType` prop (defaulting to today's
`'default'`) and have the companion stamp `'vscode'` on the sessions it
creates, then scope the history dropdown to that source. The host also
supplies a stable daemon `clientId`, which the bootstrap previously
declared but never sent.

Web Shell localizes its own surface from the `language` signal while the
companion's chrome was hardcoded English, so a zh-CN panel rendered a
Chinese transcript under an English header, history dropdown, onboarding
screen, and account dialog. Route that chrome through a small string table
driven by the same signal, including the host-only slash entries.

Also fix accessibility defects in the history dropdown: rename and delete
were revealed on hover alone and unreachable by keyboard, date headers sat
inside `role="listbox"` as invalid non-option children, arrow-key roving
stopped at group boundaries, `aria-modal` had no focus trap, and a primed
"Delete?" survived both search changes and the pointer leaving the row.

Formatting: `FileMessageHandler` and `SessionMessageHandler` were left
unformatted earlier in this branch and failed the Prettier gate.

* refactor(vscode): drop code orphaned by the WebShell cutover

The webview entry now renders EmbeddedApp against the daemon, which left
the ACP-era hook layer unreachable: nothing imports acpTranscriptAdapter,
useWebViewMessages, useAcpTranscript, useToolCalls, useSessionManagement,
useMessageHandling, useFileContext, useImage, or the permissionTypes added
by this branch. A reachability walk from webview/index.tsx reaches eight
modules; every reference to the rest comes from inside the orphaned set
itself, so it deletes as a closed unit.

EmbeddedWebShell goes with them. It was the host-driven entry point from
the earlier stage of this branch, superseded when EmbeddedApp moved to
WebShellWithProviders, and has had no consumer since — only its own DOM
test and a barrel export.

Also harden the daemon process lifecycle. `start()` returned the cached
runtime without comparing the workspace, so in a multi-root window the
second folder's chat silently reused a daemon bound to the first and
scoped every session, history page, and prompt to the wrong root. Bind the
daemon to its workspace and respawn on a change, keep a superseded child's
late exit from tearing down its successor, and report a post-startup exit
to the webview instead of leaving it fetching against a dead port.

* docs(vscode): describe the daemon architecture the cutover actually ships

The design doc still recorded the plan this branch started from: keep ACP
as the runtime boundary, add no daemon server or loopback port, and treat
"replacing ACP with daemon HTTP/SSE" as a non-goal. The final stage did
exactly that, so the document argued against the code beneath it.

Record the decision and its consequences instead — two processes per
workspace, a daemon shared with the CLI and browser Web Shell, the vscode
source type that keeps the panel's history its own, workspace rebinding in
multi-root windows, and the turn-driven host features that stopped firing.

* fix(vscode): repair round-2 review findings on the web-shell cutover (#9811)

- closeDiff now resolves workspace-relative paths the same way showDiff
  does, so permission-cycle diffs opened from daemon-relative paths can
  actually be matched and closed
- a superseded or disposed daemon child no longer reports its exit as a
  crash of the live daemon
- authCancelled no longer hides an already-authenticated session behind
  onboarding; only an unknown auth state settles to unauthenticated
- selection-only activeEditorChanged events no longer undo an explicit
  active-file exclusion
- prepareSubmit dedupes mentions in both path spaces and matches typed
  references on a whole-reference boundary
- permission diffs open only from the SDK's authoritative file_diff
  preview (writes included, model-controlled toolCall mining removed)
- the webview HTML carries VS Code's locale so chrome strings localize
- discontinued qwen-oauth models are no longer re-applied through the
  new-session initial-model route

* fix(vscode): repair round-3 critical findings on the web-shell cutover (#9811)

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

* fix(web-shell): import daemon-react-sdk from web-shell instead of webui

The cutover branch dropped the ./daemon-react-sdk export from @qwen-code/webui,
but the TerminalPanel merged in from main still imports it, breaking the
web-shell vite build (Missing "./daemon-react-sdk" specifier). Point the import
and its test mock at @qwen-code/web-shell/daemon-react-sdk, which re-exports the
same useWorkspace hook and matches every other web-shell call site.

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

* fix(vscode): close WebShell UI regression gaps

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

* fix(vscode): initialize WebShell refs explicitly

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

* fix(vscode): narrow queued prompt edits

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

* fix(release): enumerate actual npm workspaces

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

* fix(web-shell): include hasOlderHistory in the render-item callback deps

The renderItem useCallback reads hasOlderHistory to gate the edit action
but omitted it from its dependency array, failing CI's
react-hooks/exhaustive-deps gate.

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

* fix(web-shell): report each connection error once to stop the inline onError re-render loop (#10454)

* fix(web-shell): report each connection error once to stop the onError re-render loop

While a connection error persists (e.g. the daemon is unreachable), the
error-notification effect re-fires whenever the onError callback identity
changes. Hosts such as the VS Code embedded app pass an inline onError and
update their own state when it fires, so every notification triggers a host
re-render that hands the effect a fresh callback identity — re-notifying the
same persistent error forever (#10406).

Track the last reported connection.error value in a ref and notify only when
the value changes, resetting the tracker once the connection recovers. This
guards every inline-callback consumer, not just memoized hosts.

Fixes #10406

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

* fix(web-shell): only stamp the dedup ref once an onError handler exists

Stamping lastReportedConnectionErrorRef before delivery meant a host
that attaches onError after a persistent connection error appeared never
received it: the no-op delivery already marked the error as reported.
Guard on the handler first and add a regression test covering the
late-attach case (red when the guard is removed).

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

* docs(web-shell): document the onError dedup contract and fix comment wording

Describe the reported-once-per-distinct-error semantics, the reset on
recovery, and that replacing the handler mid-error does not re-deliver.
Reword the effect and test comments to describe the host class instead
of naming the VS Code embedded app, which passes a useCallback handler.

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

* test(vscode): mirror the web-shell value-dedup in the EmbeddedApp mock

The WebShellWithProviders mock re-notified on every onError identity
change, mirroring the loop App.tsx can no longer produce. Rewrite it to
report each distinct error value once (resetting on recovery), keep the
loop guard as a regression tripwire, exercise it with a changing
callback identity plus a post-delivery effect re-run, and refresh the
handleShellError comment that still cited the old loop as the
memoization reason.

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

* test(vscode): cast the captured onError prop for the mock wrapper

CapturedProps is an unknown index signature, so the destructured
onError needs the same cast the previous mock applied inline to stay
callable under tsc.

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

* test(vscode): bail the EmbeddedApp mock before stamping when no onError exists

The mirrored dedup effect stamped lastReportedError and counted a
notification even when no handler was attached, while App.tsx returns
before stamping on that path. Add the same early return so a handler
attached mid-error still receives the persistent error, and pin the
no-handler no-stamp behavior with a test that fails if the guard is
removed.

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

---------

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

* fix(web-shell): remove duplicate history dependency

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

* fix(vscode): close remaining WebShell cutover regressions

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

* fix(vscode): keep permission diff handling host-scoped

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

* test(vscode): remove orphaned completion trigger test

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

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-29 16:37:47 +00:00
Harjoth KharaandShaojin Wen 5f0814d319 fix(web-shell): replace the boot white screen with a recoverable error state (#10215)
A Web Shell tab that fails to boot — a dev-server restart invalidating
module URLs mid-load, an unreachable daemon, a rotated access token —
stays blank forever with no error and no recovery affordance. index.html
has no boot fallback, and `main()` only reaches `createRoot().render()`
after the daemon-token handshake settles, so the ErrorBoundary in
main.tsx cannot cover any of it.

Add a dependency-free ES5 watchdog to index.html: it renders a
theme-aware fallback with the captured errors and a reload button, either
immediately when a script or stylesheet fails to fetch, or after a 15s
grace period if #root is still empty. Both terminal states — a real mount
or the fallback being rendered — uninstall the watchdog, so nothing
survives a settled boot.

The watchdog script deliberately runs before #root is parsed so it is
already listening while the module graph loads, which means #root is
resolved lazily throughout and the mount observer is installed on
DOMContentLoaded rather than at script execution time; a MutationObserver
constructed against a not-yet-parsed #root is silently dropped, taking
the uninstall-on-mount path with it. index-html-boot.test.ts pins this
against a really parsed document rather than a hand-assembled one.

Boot can also outlast the grace period, in which case the panel is on
screen when React finally mounts. React appends to its container rather
than replacing it, so main.tsx now drops the panel before rendering.

Fixes #9253

Co-authored-by: Shaojin Wen <[email protected]>
2026-08-29 09:13:45 +00:00
b306bd2d46 feat(web-shell): optimize session overview table (#10115)
* feat(web-shell): optimize session overview table

* fix(web-shell): address session overview review feedback

* fix(web-shell): sync session overview catalog mutations

* fix(web-shell): address session overview round-2 review feedback

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

* fix(web-shell): address session overview round-3 review feedback

* chore: remove unintended core formatting diff

* fix(web-shell): stabilize session overview navigation

* fix(web-shell): restore session overview page scrolling

* fix(web-shell): preserve session overview safeguards

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-29 06:24:09 +00:00
Bob.qwencodeandgithub-actions[bot] b2a5b1487c chore(release): v0.22.3 (#10424)
* chore(release): v0.22.3

* docs(changelog): sync for v0.22.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-28 17:50:04 +00:00
d853f09f52 feat(web-shell): add opt-in interactive browser terminal (#9984)
* feat(web-shell): add opt-in interactive browser terminal

Adds a browser terminal to the web-shell right panel, backed by shared node-pty sessions exposed through an authenticated /terminal WebSocket route on the serve daemon. The feature is opt-in (QWEN_WEB_TERMINAL=1 plus bearer auth) and gated behind a web_terminal capability so default deployments never expose the entry point.

Terminals are workspace-scoped: the daemon resolves the client workspace selector against the live registry, binds each PTY to that runtime's cwd and effective environment, and releases sessions when the workspace drains. Reconnects claim the same session id and replay bounded scrollback (including exit state) without duplicating output; controls resize/release ride text frames while PTY data stays binary. A session with no listeners is reclaimed after 15 minutes.

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

* chore(vscode): update third-party notices

* test(cli): include web terminal capability

* test(integration): include web terminal capability

* fix(web-shell): harden terminal lifecycle

* docs(serve): document web terminal capability

* fix(web-shell): address terminal review findings

* fix(web-shell): preserve terminal sessions and key events

* fix(web-shell): harden terminal session cleanup

* fix(web-shell): make terminal limits recoverable

* fix(web-shell): isolate terminal sessions

---------

Co-authored-by: 钉萁 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-08-28 10:15:27 +00:00
Bob.qwencodeandgithub-actions[bot] 22df14223a chore(release): v0.22.2 (#10166)
* chore(release): v0.22.2

* docs(changelog): sync for v0.22.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-26 13:39:29 +00:00