mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
a8eb74fdf5b60851e1fc4a8e258e043273df4411
56
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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]> |
||
|
|
f9df3df1ef |
feat(web-shell): integrate Managed sessions into the home workflow (#14316)
* feat(web-shell): integrate Managed session workflow * docs(managed-agent): align model-switch acceptance translations * fix(managed-agent): reconcile WebShell integration checks * fix(managed-agent): address WebShell review feedback * fix(managed-agent): align schema projections and CI probes |
||
|
|
a0e6678d66 |
feat(web-shell): show a reduced header on the new-task page (#14159)
* feat(web-shell): show a reduced header on the new-task page The new-task page rendered no header at all. Show a reduced one with only workspace/daemon-scoped entries (right-panel toggle, Local Control QR, workspace location); the workspace icon stays hidden because the composer below already names the workspace. The right panel offers only workspace-scoped entries (files, terminal, web preview) before a session exists, and its state on the new-task page now persists under a per-workspace draft key, surviving reloads and session switches, and migrates onto the session the first prompt creates. * fix(web-shell): preserve new-task panel ownership and migration --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
b0cee09604 |
feat(web-shell): open context details in the side panel from the ring (#14086)
Clicking the composer context ring posted a Context Usage card into the transcript, and that card's own detail action posted a second one, so checking usage a few times left several near-identical stale snapshots interleaved with the conversation and pulled the view back to the tail. The right panel already carries a single-instance context usage tab keyed per session, with the full breakdown, refresh and compression. Bind the ring click and the transcript card's detail action to it instead. The snapshot survives only for a composer whose host offers no details surface, which today is just the embedded side-task pane; typing /context still prints into the transcript. Closes #14051 Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
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]> |
||
|
|
72316e6608 |
feat(web-shell): Reuse standard UI for Managed Agents (#13973)
* feat(web-shell): Reuse standard UI for Managed Agents Reuse native chat, model and Workspace controls for Java Managed Agent sessions while preserving admission retries and scoped drafts. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): Clear accepted drafts after editor remount Keep accepted input ownership across session switches and response-loss retries. Snapshot original editor text in the local pending record so a reload can clear matching owner storage without an in-memory callback. Preserve original commits, foreign drafts and newer different drafts. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): Clarify abandoned first-input admission Warn that first input may already be running and a new request can repeat its effects. Refresh the owning Session list after local abandonment and show uncertain recovery controls only once the active request settles. Add the focused review regressions for composer input and standalone UI. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
364593cb25 |
feat(web-shell): add action reminders and in-panel notification access (#13832)
* feat(web-shell): notify for approvals and user questions * fix(web-shell): isolate notification help and authorization attempts * fix(web-shell): preserve notification authorization and keyboard focus --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
f2e069061a |
feat(web-shell): add adaptive navigation rail and unified Live settings (#12943)
* feat(web-shell): add adaptive navigation rail and unified Live settings
* style(web-shell): add space below the Home action section
* Revert "style(web-shell): add space below the Home action section"
This reverts commit
|
||
|
|
6310dd38d5 |
feat(managed-agent): project durable tool results to WebShell (#13037)
* feat(managed-agent): project durable tool results to WebShell * codex: address PR review feedback (#13037) * fix(managed-agent): sync transcript exclusion text and localize forbidden output reads - Name item.tool_result.updated alongside the other transcript-excluded event types in the WebShell transcript contract description, the generated client comment, and the event-replay design notes, keeping the three surfaces in sync as the event-replay post-merge review required. - Explain the 3-byte UTF-8 page-boundary priming in ManagedToolResultPanel. - Map HTTP 403 to managed.result.forbidden in ResultError instead of rendering raw error text, with a regression test. * codex: address PR review feedback (#13037) * codex: address PR review feedback (#13037) * codex: address PR review feedback (#13037) |
||
|
|
5df67177df |
feat(web-shell): prepare updates and restart from sidebar (#12734)
* feat(web-shell): prepare updates and restart from sidebar * test(serve): include daemon update in capability contracts * test: complete update contracts and isolate shell output fixtures * fix(web-shell): preserve task URLs and check update launchers * fix(serve): support older Node declarations in update checks |
||
|
|
0ab64d5066 |
feat(live): stream shared screens into browser voice calls (#12623)
* feat(live): stream shared screen into browser voice calls * fix(live): clear provider readiness after interrupted startup * fix(live): preserve call intent and clean up forced stops * fix(web-shell): keep idle browser host takeover available --------- Co-authored-by: qqqys <[email protected]> |
||
|
|
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 |
||
|
|
8fb6ade2e4 |
feat(web-shell): inspect session tool calls by prompt (#12466)
* feat(web-shell): present structured shell execution results * fix(shell): preserve cleanup notices and clarify result fallbacks * fix(test): sync shell result source mappings * fix(web-shell): address shell command card review findings (#12311) - Documents: stop duplicating the command above the fallback text and label an empty legacy result as 'No output' (maintainer N1/N2) - Escape control and bidi characters at every shell-card render site while keeping clipboard copies byte-exact - Render a string rawOutput display instead of model-facing content so background-promotion and refusal messages stay visible - Show a finished command's elapsed time from startTime/endTime - Suppress the Command section when the legacy envelope already leads with the same command, keeping it once per expanded card - Pin the wasCancelled status, the background timeout gates, the synthetic exit-zero headline and the status icons; freeze the running-elapsed fixture's clock - Tag the card smoke test and wait for the SSE connection before driving live frames - Drop the dead .expandedBash selector and align both design docs with the undisclosed live-frame transport for line/byte counts Co-authored-by: Qwen-Coder <[email protected]> * test(core): isolate gitPush tests from host git config (#12311) The deterministic verification gate ran the core suite on a persistent runner whose real ~/.gitconfig carries remote.pushDefault=gd, and the three gitPush tests that resolve the push remote failed with 'fatal: gd does not appear to be a git repository'. gitEnv strips only the GIT_CONFIG_* env overrides, so the code under test still honors the host HOME config — the exact gap the file's hermeticEnv() helper documents and every gitPull test already guards against. Pass hermeticEnv() at all six gitPush call sites so host config can no longer steer push resolution. Reproduced with a poisoned HOME (remote.pushDefault=gd): the three gate failures appear on the pre-fix tree and the file is 107/107 green after, in both clean and poisoned environments. Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> * fix(core): stage browser-use runtime on demand in core prebuild (#12311) The deterministic verification gate rebuilds core with a scoped npm run build --workspace packages/core, but the review-address job installs with QWEN_SKIP_PREPARE=1 and restores only the root and core dist artifacts, so packages/browser-use/dist is absent and the core build dies in copyBrowserUseAssets. The dependency on the built browser-use runtime was only expressed through the root build's ordering, leaving every scoped core build (the gate's, or a developer's on a prepare-skipped install) broken. Add a prebuild that stages the browser-use runtime only when its dist/index.js sentinel is missing. The root build keeps building browser-use first, so the prebuild is a no-op skip there and the fallback fires exactly when the runtime is absent. Probe matrix: scoped core build fails without the runtime pre-fix (the gate rejection, reproduced locally), passes with the runtime absent (fallback stages it), and passes unchanged with the runtime present (skip path). Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): treat empty string shell display as authoritative (#12311) A persisted empty display string from a silent command is falsy, so the card fell back to the model-facing envelope in content and the legacyOutputRepeatsCommand guard then suppressed the Command panel, rendering a bare envelope with no copy action. Gate the string branch on the type alone so an empty string renders as empty output next to the command. * fix(core): preserve shell display and recording diagnostics * feat(web-shell): add session tool calls panel with prompt selection * fix(web-shell): address tool calls review findings * fix(web-shell): keep prompt text out of persisted dock state * fix(web-shell): restore sender tool call identity and review fixes * docs(web-shell): record real daemon sender verification --------- Co-authored-by: 钉萁 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen Autofix <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
906418aa9b |
fix(web-shell): make mobile access opt-in and simplify split composer (#12537)
Co-authored-by: 钉萁 <[email protected]> |
||
|
|
41f9a02efe |
feat(web-shell): add host controls for model management (#12345)
* feat(web-shell): add host controls for model management * refactor(web-shell): separate model policy from queue dispatch * fix(web-shell): close model-management policy gaps from review (#12345) - isModelSetupCommand now mirrors the daemon's tokenization and altNames, so /login, /connect and whitespace after the slash can no longer slip past a host that disabled model setup - refuse host-disabled setup commands in submitUserMessageEdit before the rewind, closing the edit-and-resend entrance (and its retry arm) - a refused side-task initial prompt is now terminal for the tab: the panel notifies the app, which drops it from the live tabs and the per-session bucket mirror instead of re-arming the toast on every revisit - the mid-turn path checks the caller policy before reading or uploading annotated files, and a failed compensating attachment delete is surfaced instead of swallowed - unmask the /auth suggestion-list assertion (it previously passed via hiddenSlashCommands, not the policy clause) and extend the interception tests to the alias inputs - pin the App->SplitView and ArtifactPanel->SideTaskPanel policy hops - e2e: tag the policy-enforcing cases @smoke so the PR gate runs them, keep evidence screenshots inside the git-ignored tree, and drop the request-level assertions the mock daemon's recorder cannot see (the /prompt half of the /auth refusal check is live and stays) * fix(web-shell): let host override /auth before the model-setup refusal (#12345) Address review feedback on the model-management controls: - Run the policy guards after the host's documented onSlashCommand override (and below ChatPane's runtimeStopped fence) instead of above it, so an allowAdd:false host keeps its own /auth entrance; the refusal still lands ahead of hidden-command daemon forwarding. - Classify typed commands by the resolved session command (exact name, then altNames, case-sensitively) instead of the bare lowercased name: a project/user command shadowing auth/connect/login stays runnable, and /AUTH reaches the daemon as prose. Guards and suggestion filters share the one predicate; an unloaded snapshot still fails closed. - Refuse /btw side <setup command> before a side-task session is provisioned; the panel's initial-prompt guard remains the backstop. - Scope the compensating attachment-delete failure report to the owning session and treat a fulfilled removed:false as a failure. - Pin the refusal toast in the App-level tests, use the maintained promptRequests() matcher with a poll and a positive control in the e2e spec, and sync README + both design docs with the intercepted set, the ordering, and the inline-edit boundary row. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): handle model policy refusals consistently * test(web-shell): run remaining model-management e2e arms at PR time Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): fail closed on model setup when the ready snapshot has no auth entry (#12345) The pane menu filter and the typed-submit guards classify through isModelSetupCommand, which returned false when the command snapshot was ready but carried no auth entry (slashCommands.disabled or the SSH policy whitelist). The pinned local /auth entry stayed listed and dispatched with no refusal. Treat an unresolved bare setup name on a ready snapshot as the absent builtin and refuse it; a project/user shadow still resolves and stays runnable. Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: 铃蓝 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> |
||
|
|
abd2ece552 |
feat(web-shell): search within the current conversation (#12234)
* feat(web-shell): add in-conversation content search * fix(web-shell): move conversation search into timeline rail * fix(web-shell): align search entry and stabilize search dialog * fix(web-shell): honor search snapshot contract and export budget * feat(web-shell): expose persisted message navigation to hosts * fix(web-shell): preserve navigation when rejecting stale requests * fix(web-shell): address conversation search review regressions * fix(web-shell): preserve and expose search result selection * fix(web-shell): preserve search navigation across viewport transitions * fix(web-shell): settle cancelled and recovered search selections * fix(web-shell): deduplicate live conversation search echoes * fix(web-shell): preserve search render and live selection state * fix(web-shell): cancel search navigation across reconnects |
||
|
|
43868d6a69 |
feat(web-shell): add shared URL navigation for pages and sessions (#12499)
* feat(web-shell): add shared URL navigation for pages and sessions * test(web-shell): expect settings route after remote connection return * fix(web-shell): preserve navigation state across workspace flows --------- Co-authored-by: 铃蓝 <[email protected]> |
||
|
|
97cb625888 |
fix(web-shell): keep mobile composer controls reachable above a soft keyboard (#12462)
* fix(web-shell): keep mobile composer controls reachable above a soft keyboard Real-device testing of the touch composer on Android Chrome with Gboard found three layout problems once the soft keyboard shrinks the viewport: - The workspace/Git row sits inside the composer's height cap, so with the keyboard open the attachments strip collapsed to 16px and attached files could be neither seen nor removed. The cap now includes that row's height. - History search opens upward from the composer with a fixed cap, so its search field and close button slid under the header. The panel is now also limited to the room above the composer, the same way the @ panel is. - The editing row overlapped the workspace/Git row by 8px, so tapping the bottom of a workspace or branch chip recalled a history entry instead. * fix(web-shell): keep mobile history search inside the visible chat area Bound the history-search panel by the real clipping edge above the composer (the chat pane under the header, or a host-declared --web-shell-popover-safe-top) instead of a 48px guess. When not even its 96px minimum fits, the panel moves down over the composer so the search field and close button stay below the header. Re-measurement is coalesced to one pass per animation frame. Measure the workspace/Git row instead of assuming 44px: with a second workspace or a long branch it wraps onto two lines. The cap also adds the 8px pull-up the editing row gives up below the row, and the editing row keeps that pull-up when no workspace row is shown, as on main. A declared zero safe top is now kept as zero; the @ reference panel reads the variable through the same helper. * refactor(web-shell): share the popover floor and fit the history panel imperatively Round-2 review follow-up: - Export POPOVER_MIN_HEIGHT from popoverRoom.ts and use it for both upward-opening popovers instead of two bare 96 literals. - Derive the history panel's room/shift from its rendered height (a no-match panel is just the search bar) and write the two custom properties straight to the panel node, so per-frame composer movement no longer re-renders the whole composer through state. - Observe the panel itself so a change in match count re-measures. - Name the mobile composer cap once as --chat-editor-composer-cap and derive the :has(.mobileContextRow) override from it. - Pin the ResizeObserver re-measure paths with target-tracking stubs, pin auto as a clipping overflow value, and add a 412x360 e2e asserting the workspace row never shrinks the attachments strip. - Qualify the soft-keyboard acceptance criterion with the verified height and document --web-shell-popover-safe-top in the design docs and the web-shell README. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): focus history search after input mounts * test(web-shell): cover history search in short desktop windows --------- Co-authored-by: probe <probe@local> Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
55d0a637ee |
feat(web-shell): support host settings item allowlists (#12328)
* feat(web-shell): support host settings item allowlists * fix(web-shell): match every setting alias and warn on unknown item ids Review follow-ups for the host settings allowlists: - isSettingVisible collects every alias mapped to a key instead of the first, so a second stable alias keeps working; exclusion still wins. - Warn once per unrecognized include/exclude id in development instead of dropping it silently (dev-only, compiled out of the published build, no new exports). - Pin the allowlist permit direction for settings-launched pickers and Add Model, the builtin:connections filter mapping, and the full published alias-to-key table. - Make the settings browser spec fully descriptor-driven instead of matching on case names. - Cross-link the superseded exclusions design doc and note that builtin:connections only renders in the standalone build. * fix(web-shell): ship the unknown settings id warning in production builds The warnUnknownItemIds guard was gated on import.meta.env.DEV, which the library build folds to false, so the whole diagnostic was dead-code eliminated from dist/index.js — the only artifact embedding hosts install. A runtime typo in includeItems/excludeIds then rendered the generic empty state with no console diagnostic. - Drop the DEV gate; the once-per-id dedup keeps production output to one line per distinct unknown id, and empty lists still never warn. - Run the warn-once test under both DEV modes via vi.stubEnv so the shipped behavior is pinned (red on the gated source when DEV=false). - Assert the excludeItems half of the validation loop with an unknown exclusion id (the spread was previously unwitnessed). - Pin the permit direction for the settings-launched voice picker: open it under includeItems: ['setting:voice-model'] and keep it open across a still-including rerender. --------- Co-authored-by: 铃蓝 <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> |
||
|
|
1672eb7934 |
feat(web-shell): expose assistant turn settlement lifecycle (#11251)
* feat(web-shell): expose assistant turn settlement
* fix(web-shell): harden turn settlement callback
* fix(web-shell): skip unused settlement observer
* fix(web-shell): correct turn settlement message and replay delivery
- project the settled message off the raw blocks with the SDK's own
final-visible-assistant predicate; a promptId-filtered re-projection
through the render adapter dropped every tool block of the turn (the
reducer never stamps promptId on tool blocks), merged the turn's text
halves under the first block's id, and promoted subagent-owned text
- publish the settlement where a replayed terminal settles a locally
bound prompt: the snapshot is released after injection and SSE resumes
from lastEventId, so that terminal never reaches the live-loop publish
and the host callback never fired for it
- default the settlement error code to 'turn_error', matching the
DaemonHttpError that matchTurnEvent rejects the submitter with
- build the published host event field by field so an internal-only
field cannot widen the public contract silently
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtrow8ztpw
* fix(web-shell): preserve turn settlement across reloads
* fix(web-shell): omit unsettled assistant message
* fix(web-shell): retire abandoned prompts and pin turn-settlement gates
Retire locally-bound prompt keys whose terminal is destroyed rather than
delivered. An epoch reset records the bound prompt and, after the reload's
replay injection, publishes a `failed` settlement when the fresh snapshot
carried no terminal for it; auth/terminal-error cleanup and
heartbeat-detected missing sessions retire the bound prompt directly. The
removePendingPrompt stale-session branch now notifies prompt removal with
the foreign session id so the ledger delete keys the right session, and the
settlement projection restores the whitespace-only assistant block skip.
Add discriminating tests: replayed terminal after epoch reset, failed
retirement when the terminal is destroyed, replayed cancelled terminal, and
stale-session removal notification.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtsl1npgrb
* fix(web-shell): keep turn settlement daemon-authoritative
* docs(web-shell): document settlement deduplication
* fix(web-shell): prettier-format the WebShell README table after adding the onAssistantTurnSettled row
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmtss6ux5rn
* fix(web-shell): define the split-pane active-prompt bridge mock in App tests
The split-session pending-panes test asserts `mockUseDaemonActivePromptBridge`
but the mock was never declared, so `Test (ubuntu-latest, Node 22.x)` failed
with `ReferenceError: mockUseDaemonActivePromptBridge is not defined`. Declare
the hoisted mock, route it through the session-catalog-hooks module mock, and
reset it in beforeEach with a return of the session active-prompt state.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtt6h9fssa
* fix(web-shell): retire destroyed-turn settlements at the abandonment sites
Restore the retirement that
|
||
|
|
8e9e9887d1 |
fix(web-shell): allow sequential SDK requests during session creation (#11913)
* fix(web-shell): allow sequential SDK requests during session creation * fix(web-shell): clarify creation timeout guarantees and pin deadlines * fix(web-shell): clarify restore budgets and verify timeout headroom * test(web-shell): measure both session creation request budgets * fix(web-shell): preserve navigation during session creation * fix(web-shell): preserve workspace selection during session creation --------- Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
535dfd9a11 |
feat(web-shell): add PWA installability and Android development shell (#11722)
* feat(web-shell): add installable PWA support * fix(build): skip incomplete Web Shell assets when bundling * feat(web-shell): declare browser support matrix and compat fallbacks * feat(web-shell): make the Web Shell installable as a PWA Serves /manifest.webmanifest and /sw.js pre-auth, registers the worker deferred to load, and replaces the earlier service-worker.js draft with sw.js as a Vite entry (PNG icons included). * feat(mobile-shell): add Android WebView shell skeleton WebView shell around the daemon-served Web Shell (no second UI), profile tuples of URL/token/display name with #token= fragment delivery, native WebView version check at the Chrome 107 floor, and a foreground-service skeleton for SSE/notifications. * chore(web-shell): drop superseded service worker draft files * test(web-shell): cover service worker routing and lifecycle * chore(web-shell): align @playwright/test with main lockfile * chore: register mobile-shell importer in pnpm lockfile * test(web-shell): parse sidebar stylesheet with postcss for @supports nesting * chore: register mobile-shell in pnpm rewrite set * fix(web-shell): address PWA and Android acceptance blockers * docs(mobile-shell): make remaining native acceptance limits explicit * fix(web-shell): address fourth PWA review round * fix(mobile-shell): restore web view on back --------- Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
b82e02fa37 |
feat(web-shell): support host settings item exclusions (#11975)
* feat(web-shell): support host settings item exclusions * test(web-shell): capture settings panel with host exclusions in visuals The visual preview flagged that no scenario renders the settings panel, leaving the host exclusion feature invisible to the before/after preview. Add a settings harness page that maps ?exclude= onto the host-only settings prop (the standalone entry never passes it) and two scenarios: the default panel and the panel with representative exclusions, gated on row/category assertions so a filtering regression fails loudly. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): scope settings-exclusion dialog closes to the owning item The exclusion force-close effect ran blanket resets on every dialog state, so a stale settings key closed pickers later opened from a command or the status bar, and the /model branch-head reset erased the key on sub-paths that open no dialog, disarming exclusion for a dialog that was still open. Close only the dialog the held key owns, clear the key only where /model takes over a dialog surface, share one builtin-id predicate, and drop the duplicated empty-state description. Pin each behavior with mutation-probed tests. * fix(web-shell): cover live settings exclusions and host forwarding * fix(web-shell): close settings-exclusion gaps from review (#11975) - add the missing setting:omni alias for omni.enabled, plus a cli-side drift test comparing the live served settings set against the published alias table in both directions - pin the App -> SettingsMessage presentation handoff - scope command-takeover clears to the surface each command owns; drop the bare-/model and --voice clears that could only disarm a settings-launched surface - pin the close-ladder arms, builtin item ids, nav badge/scoping, card gate, and user-scope exclusion with falsifiable tests - stop inventing the Model category during the initial settings load - promote the README section to a top-level Chinese section with prop rows, and make the visual harness theme assertion falsifiable * fix(web-shell): drop duplicate omni settings alias (#11975) The merge of the two parallel fix branches kept both 'setting:omni-media-delivery' (maintainer's) and 'setting:omni' (bot's) as public aliases for omni.enabled. Keep the maintainer-chosen descriptive alias and remove the redundant one before the new alias API ships; repoint the omni witness test at the surviving alias. * test(web-shell): close settings-exclusion test gaps from review (#11975) Address the remaining review findings on the settings presentation exclusions PR: - Pin the scoped /auth takeover against over-clearing: a settings-launched fast picker (held key 'fastModel') must still close when its alias is excluded after /auth ran, and must stay open across /auth without an exclusion. The existing auth fixture held exactly the key /auth clears, so scoped and blanket clears were indistinguishable. - Make the served/rendered alias drift test reject two stable IDs mapping to one configuration key; the membership checks alone cannot see a duplicate, and a published duplicate cannot be withdrawn. - Move gotoSettingsHarness into the visuals harness next to the other navigation helpers and add it to the freeze-before-navigate contract. - Gate openSettingsPanel on a descriptor-derived nav button instead of a row, so reordering the fixture's first General descriptor cannot race the settings fetch. - File the showTimestamps fixture row under General (matching the real schema) instead of an invented Output category, and make the chat-width absence assertion select the UI category first so it is no longer vacuous. * test(web-shell): pin partially excluded category in settings visuals (#11975) The host-exclusions capture pinned whole-category dropout (Tools) and a builtin partial exclusion (builtin:chat-width in UI), but not a partial exclusion of a descriptor row: with output.showTimestamps filed under General in the real schema, excluding setting:timestamps must remove only that row while the category and its surviving rows stay. The two added assertions also restore the spec's declared assertion surface after the previous commit moved gotoSettingsHarness (and its two assertions) into the shared visuals harness, which the deterministic gate measured as a net loss of two assertions. --------- Co-authored-by: 铃蓝 <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen Code Bot <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> |
||
|
|
730932b87f |
fix(web-shell): honor ui.theme and general.language in the standalone Web Shell (#11961)
* fix(web-shell): let standalone entry defer to ui.theme/general.language settings
The standalone Web Shell entry (main.tsx) resolved its boot theme and
language from ?theme=/?language=, then localStorage, then hardcoded
defaults (dark + navigator.language), and passed the result down as
unconditional host props. App treats any host-supplied value as
authoritative and returns before reading the daemon's effective
ui.theme / general.language, so in Desktop and any qwen serve browser
session the settings-derived branch was dead code: the Settings panel
showed the configured values while the UI stayed dark + English.
- getInitialTheme()/getInitialLanguage() now return undefined ("no
opinion") when neither the URL nor localStorage supplies a value,
letting App resolve the effective settings (#11955).
- App reports settings-resolved values through new onThemeResolved /
onLanguageResolved callbacks so the host can sync document chrome
(html theme class, theme-color meta) without adopting the value as
its own opinion — no prop latch, no localStorage write, so a later
settings.json edit keeps applying.
- Explicit ?theme=/?language= params, stored in-app choices, and live
panel edits keep overriding settings (host-override contract).
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmu2v4pao6s
* style(web-shell): prettier-format main.test.tsx
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmu2v4pao6s
* fix(web-shell): keep provider surfaces on dark palette and browser locale without a host opinion
WorkspaceSessionProvider's loading and error surfaces render before (or
instead of) App, so they never see the settings-resolved values App
reports through onThemeResolved/onLanguageResolved. With theme/language
now undefined for a no-opinion host, they fell back to light shadcn
tokens on the dark pre-paint page and unconditional English. Restore the
pre-#11955 fallbacks for these surfaces only: the pre-paint dark default
and navigator.language.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmu34f2au75
* fix(web-shell): never persist a rolled-back settings language pick as a host opinion
handleSettingsLanguageChange optimistically notified the host through
onLanguageChange, and both rollback paths (streaming-blocked, daemon
rejection) did the same with the previous value. With no host language
opinion that value is settings-derived, so the host persisted it as its
own opinion and every later settings.json edit was silently shadowed —
#11955 again on a browser where the user never chose a language. The
optimistic pick and the rollback now steer document chrome through the
observe-only onLanguageResolved channel; onLanguageChange fires only
after the daemon accepts the change.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmu34f2au75
* test(web-shell): reset document theme classes between StandaloneApp tests
The theme-light assertion in 'adopts and persists an in-app theme choice'
was satisfied by state leaked from the preceding test, so it could not
witness setDocumentTheme in handleThemeChange. Clear the shared jsdom
document classes in beforeEach so no test inherits another's chrome.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmu34f2au75
* fix(web-shell): keep resolved appearance scoped
* fix(web-shell): keep settings themes scoped
* fix(web-shell): preserve controlled language chrome
* fix(web-shell): preserve controlled language callbacks
---------
Co-authored-by: Qwen-Coder <[email protected]>
|
||
|
|
e4755ef6ab |
feat(web-shell): customize context details and collapse snapshots (#11996)
Co-authored-by: 钉萁 <[email protected]> |
||
|
|
20ecdaf6b2 |
feat(web-shell): add web previews with saved delivery history (#11276)
* feat(web-shell): add web previews with saved delivery history
* fix(serve): register saved preview telemetry route
* test(cli): update telemetry route drift count
* fix(web-shell): reclaim snapshot files and address preview findings (#11276)
* fix(web-shell): Replace undefined mock in split rerender tests (#11404) (#11406)
* fix(web-shell): Replace undefined mock in split rerender tests (#11404)
The two "does not rerender App for other split sessions" tests referenced
mockUseDaemonActivePromptBridge, which is not defined anywhere in
App.test.tsx — a deterministic ReferenceError that failed the main-branch
CI Test job at
|
||
|
|
2f426a64f4 |
feat(web-shell): enrich browser notifications and open target sessions (#11447)
* feat(web-shell): enrich browser notifications and open target sessions Co-authored-by: Qwen-Coder <[email protected]> * feat(web-shell): include current prompt in browser notifications Co-authored-by: Qwen-Coder <[email protected]> * feat(web-shell): configure browser notification branding and defaults Allow hosts to configure notification names, icons, and the initial preference. Enable notifications by default in the built-in page while preserving saved user choices and browser permission requirements. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address browser notification review feedback Preserve code excerpts, bound pending notification text, and keep notification configuration changes from restarting session streams. Reconcile branding documentation and add regression coverage. Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address notification follow-up review cases Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> |
||
|
|
13b69b3313 |
feat(web-shell): make the product name and logo configurable (#11244)
* feat(web-shell): make the product name and logo configurable
White-labelling the Web Shell meant editing six source files and rebuilding: the
product name was a literal in the sidebar, the welcome header, the About panel
and the document title, and the logo existed as two hand-duplicated copies of the
same inline SVG.
Brand is now configuration. A name and a logo path are read from the operator
settings scopes, and a new process-global daemon route resolves the logo into a
data URI for the client. Embedding hosts get the same control through a prop that
replaces the resolved value outright. The terminal banner already had its own
white-label settings, so this follows their sanitization and path-resolution
conventions rather than inventing parallel ones.
Two decisions carry the design. The workspace settings layer is never read, so a
repository's own settings file cannot rename the product or name a file for the
daemon to inline into every connected browser. And a custom logo is only ever
rendered as an image, never injected as markup, which is what makes it safe for
the daemon to skip sanitizing the SVG it read.
An untouched installation renders exactly what it did before: the resolved brand
is empty, every consumer falls back to its own built-in literal, and the served
document keeps its original title and favicon bytes, so the existing visual
baselines stay valid.
* fix(docs): sync the daemon capability tag count with the new brand tag
The daemon index states how many capability tags the registry holds, and a
contract test fails the build when the two disagree. Adding the brand tag made
the registry 158 while the index still said 157.
* chore: re-trigger CI on the current head
The previous push arrived while a re-run of the prior commit was still holding
the workflow's concurrency slot, so GitHub suppressed the new run instead of
queueing it and the event does not replay once the slot frees. This leaves the
branch head with no CI at all. An empty commit re-fires the synchronize event;
the repository squash-merges, so it does not survive into main.
* fix(web-shell): Address brand review feedback across resolver, client and docs
- Expose a brandSettled flag from the workspace provider so a
settled-with-no-brand outcome (older daemon, withdrawn host prop) is
reported and invalidates stale cached chrome, while the in-flight
state still fires nothing
- Fall back to the built-in sidebar mark when a logo data URI fails to
decode, and when a host logo node is falsy; remount the logo image
per data URI so a decode failure cannot stick to the next logo
- Report an empty host brand name as unset in onBrandResolved, and type
the callback payload as the document-actionable subset
- Exclude the favicon from the boot watchdog's fatal-resource
classification and keep its content-free error out of the panel list
- Require a namespaced <svg> root in the resolver, cover the post-decode
size cap, and pin the exact stderr prefix the protocol doc publishes
- Assert GET /brand answers JSON on the real app for any Accept header
- Sync the capability index count (159), settings docs and the design
doc with the above
* fix(web-shell): Address second brand review round — settle semantics, SVG scanner, test fixtures
- Settle the brand fetch only on a definitive outcome: an answer, or a
404 from a route-less daemon. A retryable failure (503 bootstrap, 429,
transport, old SDK) is unknown rather than absent — settling it
cleared the pre-paint cache and reset the tab title mid-session over
a blip nothing retries (review Critical)
- Rewrite the SVG root scanner quote-aware: a quoted `>`, `[` or `]` in
DOCTYPE literals or the root tag no longer rejects valid documents,
and an xmlns-shaped substring inside another attribute's value no
longer certifies a namespace-less document as SVG
- Warn (not reject) when the root svg carries no viewBox and no
width/height: the browser loads such an image successfully but may
paint a blank mark at the sidebar's fixed size, and only the daemon's
stderr can say so
- Treat a nullish host brand prop as absent in the resolution gate,
matching the `??` one line above
- Pin previously unwitnessed guards: the fd identity re-check and both
fs soft-fail branches, the logo key remount, the favicon error-list
exclusion, the unsettled-to-settled transition, whole-object host
takeover against a daemon logo, and the superseded-client reset and
catch-leg disposed guards
- Drive GET /brand on a real app with the SPA fallback mounted and a
token configured, and pin the System settings layer to empty files so
a maintainer's machine-wide brand cannot leak into the assertions
- Round-trip the pre-paint cache through the real inline script instead
of pinning each side against its own copy of the key
- Document the hard-link and decoded-size rules, the namespace
requirement, the viewBox advisory, and the corrected 503 semantics
(bootstrap catch-all, retryable; a draining daemon does not reject
this route); the README's onBrandResolved row now names the narrower
payload type
* fix(web-shell): Close third brand review round — placeholder exclusion, scanner correctness, retry path
- Read brand keys from the pre-substitution snapshot and refuse
environment placeholders with a warning: placeholder substitution
draws from the process-wide environment, which a workspace populates
first at boot, so resolving brand placeholders would let a repository
supply the value — the workspace exclusion through a side door
(review Critical)
- Blank quoted SVG attribute spans per UTF-16 code unit, not per code
point: an astral character collapsed to one space and shifted every
later index, refusing valid namespaced documents (review Critical)
- Accept prefix-bound <svg:svg> roots that bind xmlns:svg to the SVG
namespace (browsers render them), keep refusing <svgfoo> near-misses
and prefix-only bindings on an unprefixed root, and pin the
whitespace-prefixed fake-xmlns shape that actually exercises the
quote-blanking
- Make the scaling-geometry advisory value-aware: an empty viewBox,
zero dimensions, or percentage width/height now warns (loads
successfully but cannot scale into the sidebar box)
- Add refreshBrand beside refreshCapabilities — same 404-only settle
rule, generation-guarded, gated to the genuinely-missing state and
no in-flight fetch — and call it from the connection recovery path,
so a retryable brand failure no longer leaves in-app chrome and tab
chrome disagreeing for the page's lifetime
- Warn on console when the sidebar logo fails to decode (the one logo
failure the daemon's root-tag validation cannot see)
- Pin the O_NONBLOCK FIFO defence with a real mkfifo fixture, the
published 429/drain transport states on the real app, and make the
ordering test's body discriminating with a fixture brand
- Document the placeholder carve-out, prefix-bound roots and the
value-aware advisory across settings docs, schema, protocol reference
and the design record
* fix(web-shell): Close fourth brand review round — env-var side door, retry machinery, scanner class fix
- Add QWEN_CODE_SYSTEM_SETTINGS_PATH / QWEN_CODE_SYSTEM_DEFAULTS_PATH to
the hardcoded project-.env exclusions: they select which file becomes
the System settings layer, so a repository could otherwise promote
its own file above the operator's User layer and rebrand the shell
with literal values (review Critical)
- Refuse brand placeholders on substitution, not syntax: run the real
resolveEnvVarsInString and refuse only when it would change the
value, so a literal name containing `$` survives while anything a
workspace-tainted environment could have supplied is refused
- Emit one stderr line per brand warning instead of a joined line, so
a log rule keyed on one key's prefix keeps firing when another
misconfiguration exists
- Make the SVG root matcher prefix-agnostic and entity-aware: any
prefix bound to the SVG namespace qualifies, character references in
attribute values are decoded before comparison, a degenerate viewBox
no longer suppresses the scaling advisory, and a prefix-bound root
with unprefixed children gets its own advisory
- Retry a retryable brand failure once after a short delay and warn
only when the retry is also refused (404 stays silent); wire
refreshBrand into the recovery path with tests; gate it on
in-flight; attribute retries per client instance; reset the brand in
the render that observes a client change so no committed frame mixes
two connections
- Pin the O_NONBLOCK flag with a flags-capture witness (the FIFO case
alone cannot fail fast: a blocked open wedges the worker's event
loop), skip the FIFO fixture on Windows where the defence does not
exist, and mock the not-stattable path instead of relying on POSIX
ENOTDIR
- Document the startup hold vs terminal 503 semantics correctly and
retire the design record's pre-retry narrative
* fix(web-shell): Close fifth brand review round — real XML parser, docs alignment, hold test
- Parse the logo with the vendored fast-xml-parser instead of the
hand-rolled root-tag scanner: well-formedness (duplicate attributes,
misplaced declarations) is now rejected by a real validator, the
namespace check reads parsed attributes with entity references
decoded, any prefix bound to the SVG namespace qualifies, and the
prefix-children advisory walks the parsed tree — construct-aware by
construction. The DOCTYPE is stripped for the validator with a
quote- and subset-aware scan because the validator's own DOCTYPE
handling is quote-blind (review Suggestion, filed as the class-level
finding)
- Bound entity decoding at Unicode's ceiling: an out-of-range
character reference no longer throws a RangeError out of the
resolver — which the route's catch-all turned into a 200 {} that
dropped the validly configured brand NAME along with the logo
(review Critical)
- Refuse brand placeholders by comparing the layer's substituted value
against its pre-substitution snapshot — the authoritative engine
WITH the home-.env fallback — and keep an unresolvable placeholder
verbatim so a typo'd variable shows as literal text; say so in all
five published artifacts, and hint when a missing logo's path still
contains a placeholder token (review Critical)
- Gate the prefix-children advisory on the default namespace being
absent: with both bindings, unprefixed children render fine
- Split the scaling advisory's message by cause (zero-area viewport,
malformed viewBox, no usable geometry) and document both advisories
- Pin the startup-hold guarantee with a real deferred-runtime test:
GET /brand issued before the runtime is ready is held and answered
200 once it settles
- Drive the two retry tests on fake timers (BRAND_RETRY_DELAY_MS is
exported for them) instead of sleeping through the production
constant, and pin the route's one-stderr-line-per-cause invariant
with a two-misconfiguration case
* fix(web-shell): Close sixth brand review round — saxes parser, lockfiles
Swap fast-xml-parser (subset validator plus pre-decoded attributes) for
saxes, the streaming XML parser jsdom uses: malformed documents are now
refused instead of shipped, attribute values are entity-decoded exactly
once so the xmlns gate cannot be faked by a second decode, and the parse
stream is closed after write so an unclosed root fails at EOF. Regenerate
both lockfiles for the dependency swap — the round-5 fast-xml-parser edge
was never recorded, which is what turned the frozen pnpm installs red —
and pin the refused well-formedness classes and the double-decode fixture
in the resolver tests.
Co-authored-by: Qwen-Coder <[email protected]>
* fix(web-shell): Enable namespace processing in the brand logo parser
saxes was constructed with default options, so an undeclared namespace
prefix on a child (the classic legacy-export xlink:href with no
xmlns:xlink) parsed clean here while browsers fatal on it — the daemon
shipped a data URI no browser renders, with nothing on stderr. Enable
xmlns: true, matching jsdom (browsers always have namespace support),
flatten the object-shaped attribute values that mode returns, and pin
both sides: undeclared child prefixes refuse, implicitly-bound xml: and
properly declared bindings still accept. Mutation-checked: reverting the
option and the flattening turns the new refusal test red.
Co-authored-by: Qwen-Coder <[email protected]>
---------
Co-authored-by: Qwen-Coder <[email protected]>
|
||
|
|
a0f0d38d4c |
fix(web-shell): avoid duplicate cold session restoration (#11413)
* fix(web-shell): avoid duplicate cold session restoration * fix(web-shell): expose known capabilities on initial connection * fix(web-shell): recover from initial workspace discovery failures * fix(web-shell): preserve discovery errors and stabilize history smoke * fix(web-shell): retain localized workspace error guidance --------- Co-authored-by: probe <probe@local> |
||
|
|
a7a6addd4e |
feat(web-shell): add opt-in browser task notifications (#11398)
Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
663e55bd29 |
feat(web-shell): refine file cards and configure drop destination (#11298)
* feat(web-shell): customize artifact icons * fix(web-shell): preserve artifact icon fallbacks * fix(web-shell): improve artifact fallbacks * feat(web-shell): refine file cards and configure drop destination * fix(web-shell): address file card and drop review feedback --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
36f2c14f0e |
feat(web-shell): align sidebar session scopes (#11240)
* feat(web-shell): add composer add menu * test(web-shell): target composer history search * feat(web-shell): align sidebar session scopes * test(web-shell): stabilize sidebar smoke coverage --------- Co-authored-by: 钉萁 <[email protected]> |
||
|
|
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 |
||
|
|
f4b49b0489 |
feat(web-shell): expose session artifact snapshots (#10747)
* feat(web-shell): expose session artifact snapshots * fix(web-shell): re-arm artifact projection after reconnect * fix(web-shell): deliver unprojected artifacts --------- Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: 易良 <[email protected]> |
||
|
|
6e99d02914 |
feat(web-shell): Add standalone chats (#10514)
* docs(plans): Add standalone PR6 WebShell UI plan Implementation plan for the final PR of the standalone daemon sessions sequence tracked by issue #8908: wiring Home/global New Chat, Recents, deep links, and project-only surface hiding to the explicit session contexts merged in #10418. * docs(plans): Address PR6 plan review feedback - Standalone creation omits workspace-only fields (sourceType/worktree/branch) - Capability read is tri-state: loading waits, only loaded-absent falls back - Pending context stays authoritative until attach commits - Live current-session New Chat routes through startLive('new') - Repair offered only for missing directories; compromised is fail-closed - Recents standalone view model never exposes the internal cwd - Creating deep links poll to a terminal state with explicit Retry - Refresh anchors for the #9811 WebShell cutover * docs(plans): Address second round of PR6 plan review - Capture prompt-admission working_directory_missing into standalone connection state; Repair is owner-guarded and reloads on success - Cancel stale deep-link resolution with an App-level route token - Exclude standalone sessions from Split View in PR6 - Clear held composer attachments on context switch; guard at submit - List archived standalone sessions in a standalone Archived lane with explicit refresh after create, prompt completion, and lifecycle actions - Interpret per-session batch outcome envelopes before updating Recents * docs(plans): Address third round of PR6 plan review - Resolve standalone deep links at the root before the session provider mounts; cold loads issue zero workspace-load requests - Give every new-session caller an explicit intent (global / inherit / workspace) instead of inferring from undefined cwd - Block every Split View ingress (footer, ?split=, sessionStorage, controlled props), not only Recents rows - Complete the outcome-unknown recovery state machine (creating / existing / absent / unknown) and block submission while unresolved * docs(plans): Address archived-summary, composer-isolation, and split-classifier review - Branch cold-link and recovery flows on isArchived: render archived state, unarchive with per-id success verification, then load - Isolate composer commands/skills/atWorkspaceCwd and draft/history identity for standalone drafts; no primary fallback - Specify the fail-closed Split View bare-id classifier before panes mount - Rewrite project/Goals examples to the explicit intent contract * docs(plans): Close remaining context-isolation paths in PR6 plan - Gate workspace effects, command discovery, and submit-time handlers on effectiveContext.kind === 'workspace', not only rendered controls - Disable the legacy global input-history fallback for standalone identities - Give ScheduledTasksDialog.onCreateViaChat an explicit workspace intent * docs(plans): Close transcript-branching and legacy dialog ingress gaps - Exclude generic transcript branching (/branch, per-message Branch) from standalone contexts; the daemon route is rejectStandalone - Gate session-overview, resume, delete, and release ingresses from non-workspace contexts; standalone /resume routes via explicit context Co-authored-by: Qwen-Coder <[email protected]> * docs(plans): Gate the workspace Web Terminal in non-workspace contexts Terminal availability and the open handler require an explicit workspace context; inherited terminal tabs are discarded when entering standalone or Live, and ordinary shell/tool commands in the private directory remain governed by the session permission pipeline. Co-authored-by: Qwen-Coder <[email protected]> * feat(web-shell): add standalone chats Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address PR review feedback (#10514) Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address PR review feedback (#10514) Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address PR review feedback (#10514) Co-authored-by: Qwen-Coder <[email protected]> * fix(web-shell): address PR review feedback (#10514) Co-authored-by: Qwen-Coder <[email protected]> --------- Co-authored-by: Qwen-Coder <[email protected]> |
||
|
|
dd5b857c8e |
feat(web-shell): allow copying terminal turn errors (#10001)
* feat(web-shell): allow copying terminal turn errors * fix(web-shell): expose turn error copy on touch devices * test(web-shell): cover turn error action visibility --------- Co-authored-by: 恪文 <[email protected]> |
||
|
|
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 (
|
||
|
|
6e878f539c |
feat(web-shell): allow hiding the session source switch (#10079)
* feat(web-shell): allow hiding the session source switch * fix(web-shell): reset hidden session source state * fix(web-shell): keep source state aligned before commit --------- Co-authored-by: 易良 <[email protected]> |
||
|
|
9a64c0a963 |
feat(serve): add pollable daemon turn status (#9080)
* feat(serve): add pollable turn-status endpoints for daemon sessions Add GET /session/:id/turns/current and GET /session/:id/turns/:promptId so external callers can poll a turn's lifecycle state (queued / running / completed / cancelled / error) and result instead of holding the SSE stream for the whole turn lifetime. - Live state comes from the bridge's pending prompt queue; settled outcomes from persisted turn_result transcript records, so results survive daemon restarts and the daemon keeps no per-turn memory - Each prompt captures its own recording and settles exactly that one, so overlapping turns (DAEMON-003 deadline overlap) can never misattribute one turn's outcome to another promptId - Enforces the same client authorization as POST /session/:id/prompt Refs #8680 * test(serve): update telemetry route count * fix(serve): prefer settled turn outcome over deadline error overlay When the prompt-deadline path latches an error terminal in the overlay and the child later settles and persists a non-error turn_result for the same promptId, the poll surface previously kept the overlay error while enriching it with the successful resultText, and flipped to completed only after overlay eviction or restart. Merge via mergeTerminalWithPersisted at the two enrich call sites so the persisted outcome supersedes a bridge-synthesized error terminal once it exists; the exactly-once turn_error event publication and FIFO release are unchanged. The different-promptId endedAt tie-break is intentionally untouched. * fix(serve): pin turn-start session identity for turn-result settle R4-1: settle resolved the ChatRecordingService at settle time, so a startNewSession rotation mid-turn could land the turn_result record in the new session's transcript while the poll surface kept reading the old one. Capture the recorder at turn start and settle on that instance; pin the outgoing service's session identity at rotation so the late append keeps the pre-rotation sessionId. R4-2: reject empty error.message/error.code in turn_result payloads, mirroring the existing empty-promptId rejection. Adds rotation/pin regression tests plus the round-4 test suggestions (extractor fallback, startedAt, cancel/error race matrix, resultCode defaulting, removed-prompt projections). * fix(serve): enforce the turn_result bounded contract on the write path R3-3: cap promptId, stopReason, and originatorClientId at 256 chars in isTurnResultRecordPayload, closing the unbounded echo of corrupted-transcript values through GET /session/:id/turns/:promptId; recordTurnResult now validates payloads against the same contract before appending, so type-correct but invalid shapes (error state without error, error on non-error states) can no longer produce records invisible to the restart scan. Also lands the four round-5 test assertions: merged-payload error-leak pin, multi-model-call settle count, successor attribution in the superseded-throws test, and the early session-mismatch guard pin. * fix(serve): address round-6 review findings on daemon turn status - Session: settle a successor-aborted turn as cancelled only when the thrown error is the abort itself; genuine failures after a NEW_PROMPT abort surface as error, matching the send-loop contract - bridge: serve repeat polls of a settled promptId from the enriched overlay instead of re-scanning the child transcript, and give the turn-status read the transcript timeout instead of the 10s init default - bridge: forward the channel display text unchanged; Session treats an empty display text as absent for the turn record ([image] fallback) - Session: cap streamed-response accumulation for turns without a channel delivery at the turn-result bound - docs: document the bounded non-monotonicity of poll terminals * fix(serve): guard turn-status reads against rewind races and keep the trusted prompt projection A successful rewind that completes while a getSessionTurnStatus child transcript scan is in flight could let the pre-rewind record be cached into the freshly cleared overlay and served forever. Track a per-session rewind generation captured before the scan and discard the scanned outcome when it moved. enrichTerminalTurnStatus and the deadline-supersede merge returned the child-recorded promptText ahead of the bridge's trusted display projection, leaking hidden channel context on the poll surface. Make promptText/promptTextTruncated backfill-only and keep the terminal's projection in the supersede path. Make the pinning test adversarial and correct a false comment about the child's ''-as-absent fallback. --------- Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Qwen-Coder <[email protected]> Co-authored-by: qqqys <[email protected]> |
||
|
|
addde7c1d0 |
feat(web-shell): decouple composer from catch-up and rebuild SSE on disconnected submit (#9323)
Catch-up (SSE history replay) no longer disables the composer input or swaps its placeholder to "加载中": replaying history does not conflict with typing, and a stuck catch-up must never lock the input. A prompt submitted while the SSE stream is down is no longer blocked with a toast: admission rebuilds the stream immediately by aborting the reconnect backoff and resuming via Last-Event-ID, keeping the session handle intact. Drops the now-unreachable 'loading' placeholder state from the public WebShellComposerPlaceholderState type and syncs docs (README, design doc, provider and entry type comments). Co-authored-by: 钉萁 <[email protected]> |
||
|
|
0a3098a279 |
feat(web-shell): add contextual task panels (#7929)
* feat(web-shell): add contextual task panels * fix(web-shell): harden contextual task panels * fix(web-shell): preserve side task titles * fix(web-shell): address review feedback on context panels PR (#7929) - Add POST /session/:id/side-task to telemetry route catalog (51 routes) - Increase SDK browser bundle size limit to 184KB - Fix duplicated data-testid="chat-pane" → "chat-pane-container" on container - Gate sourceType behind session_source_metadata capability check - Add removeSession cleanup after killSession in !res.writable path - Add i18n key sideTask.renameFailed for error fallback - Add unit tests for selectVisibleHistoryRecords invariant * fix(cli): update telemetry-catalog route drift guard to 51 routes (#7929) * fix(web-shell): address review feedback round 2 on context panels PR (#7929) - Fix /fork sider discarding createSideTask() return value: show toast when side tasks are unavailable - Fix layout feedback loop: availableWidth no longer depends on environmentPanelVisible since the CSS overlay does not change the chat pane DOM width - Remove dead environmentPanelSuppressed state (never set to true) - Restore setArtifactPanelOpen(false) in closeArtifactPanelTab when the last tab is closed - Extract agentDisplayName(task) to a local variable to avoid triple invocation per render * fix(web-shell): dedupe completed background agents in environment panel (#7929) getEnvironmentAgentTasks correlated a transcript tool card with the live /tasks snapshot only on toolUseId, the notification taskId, and a <subagentType>-<callId> derived id. A completed background agent can lose that linkage (its live task carries no usable toolUseId and its daemon id is general-purpose-<internalId>), so the trailing loop appended the live task as a second entry. Add a conservative content fallback (prompt, or description+subagentType) mirroring the daemon's legacy resolver. * feat(web-shell): support side tasks during active turns * fix(web-shell): deduplicate completed subagents and gate sourceType on capability (#7929) * fix(web-shell): restore background agent reconciliation and fix agent dedupe (#7929) Restore the one-shot subagent reconciliation for inline background Agent tool cards. Persisted notification records do not always retain a toolUseId, so the SSE discrete-notification path alone can leave a card stuck in Running; the documented fallback resolves pending cards through the subagent endpoint after catch-up, reconnect, and terminal notifications. Also stop the loose description content fallback in getEnvironmentAgentTasks from claiming a live task that another transcript tool call already links precisely (by toolUseId, message taskId, or derived id). Two agents sharing a description previously collapsed into one: the fallback stole the linked task, its owner re-matched the same task, and the orphan was dropped. * fix(web-shell): address critical review feedback on context panels (#7929) * fix(web-shell): reconcile side-task state across sessions and listings (#7929) * fix(web-shell): preserve contextual panel fallbacks --------- Co-authored-by: 钉萁 <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> Co-authored-by: qwen-code-dev-bot <[email protected]> Co-authored-by: qwen-code-ci-bot <[email protected]> Co-authored-by: Qwen Code Autofix <[email protected]> Co-authored-by: Qwen Code Bot <[email protected]> |
||
|
|
a95510cb7c |
fix(web-shell): translate legacy skills invocations (#8103)
Co-authored-by: 钉萁 <[email protected]> |
||
|
|
1d55d290a0 |
feat(web-shell): render streaming charts with markdown-chart (#7916)
* feat(web-shell): use markdown-chart for streaming charts * fix(web-shell): address markdown chart review feedback * fix(web-shell): preserve legacy chart ref caching * fix(web-shell): preserve chart safety and loader stability * test(web-shell): strengthen markdown chart safety contracts * test(web-shell): cover legacy chart streaming adapter |
||
|
|
74a786dac3 |
fix(web-shell): isolate slash command plugin pages (#7581)
Co-authored-by: ytahdn <[email protected]> |
||
|
|
fee601ae77 |
feat(web-shell): add selective shadow DOM isolation (#7551)
Co-authored-by: 钉萁 <[email protected]> |
||
|
|
3586f48171 |
feat(web-shell): support custom slash command actions (#7267)
* feat(web-shell): support custom slash command actions * fix(web-shell): apply slash handlers consistently --------- Co-authored-by: 钉萁 <[email protected]> Co-authored-by: 易良 <[email protected]> |
||
|
|
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]> |
||
|
|
b9e5629d08 |
fix(web-shell): optionally restart SSE after prompt admission (#7080)
* fix(web-shell): optionally restart SSE after prompt admission * fix(web-shell): allow prompt recovery while disconnected * test(webui): cover throwing SSE restart path --------- Co-authored-by: 钉萁 <[email protected]> Co-authored-by: Shaojin Wen <[email protected]> |
||
|
|
0c6212c0b0 |
feat(web-shell): add workspace path lock (#6853)
Co-authored-by: 钉萁 <[email protected]> |
||
|
|
71c7e448f0 |
feat(web-shell): editable user-scope settings and in-panel model management (#6768)
* feat(web-shell): editable user-scope settings and in-panel model management
Make the Settings panel able to manage ~/.qwen/settings.json (user scope),
and add model management inside the panel's Model category.
User scope: the User tab was read-only; enable writing user-scope settings
end-to-end (route allows the user scope, and the client/UI thread it through),
so the same structured controls edit ~/.qwen/settings.json.
Model management: list configured models (grouped by provider), set the
current model, add a model (reusing the existing provider-setup wizard), and
delete a model. Delete is the only new backend surface: DELETE /workspace/models
rewrites modelProviders in the owning scope, empties (rather than drops) a
provider whose last model is removed so the format-preserving settings writer
clears it cleanly, and clears model.name when the active model is deleted.
Voice Model and Model Fallbacks now use pickers instead of free text: Voice
Model reuses the voice picker; Model Fallbacks opens a multi-select (up to 3,
ordered) whose value round-trips through the comma-separated setting.
* fix(web-shell): label model sub-dialog buttons "Select" not "Edit"
Fast/Vision/Voice Model and Model Fallbacks open a picker, so their
empty-state button now reads "Select" instead of "Edit", which wrongly
implied free-text editing.
* fix(sdk): export DaemonModelDelete{Request,Result} from daemon entry
The DELETE /workspace/models types were added to daemon types but not
re-exported from @qwen-code/sdk/daemon, so consumers resolving the built
dist (webui's dts rollup in CI) failed with TS2305.
* fix(web-shell): address automated review of model management
- reset modelSettingScope when the Add Model (auth) dialog closes too, not
only the model picker / fallbacks dialog (Critical)
- delete: don't surface a reload failure as "delete failed"
- ModelFallbacksDialog: drop role=listbox/option (no arrow-key nav) for
aria-pressed toggle buttons matching the click-only interaction
- add :focus-visible outlines to model-management and fallbacks buttons
- mock daemon: handle DELETE /workspace/models
- document first-id-match-wins in removeModelFromProviders
- tests: cover 500 path, broadcast assertions, parseTarget validation codes,
active-model clearing with a pinned baseUrl, and scope the runtime-model
no-delete assertion to that row
* fix(serve): keep workspace-qualified settings route workspace-only
Widening VALID_WRITE_SCOPES to include 'user' for the primary
/workspace/settings route also loosened the trust-gated
/workspaces/:workspace/settings route, breaking its deliberate
"reject user scope" contract. Give the qualified route its own
workspace-only scope set; the primary route keeps user scope.
* fix(web-shell): address second review round of model management
- isActiveModelSelection: when the active model is pinned to a baseUrl, an
id-only delete no longer clears it (may have removed a different variant)
- DELETE /workspace/models: on a partial multi-key persist failure, broadcast
the committed writes before returning 500 (matches workspace-voice)
- model pickers (fast/vision/voice + fallbacks) read the value for the scope
being edited, so the User tab no longer shows/clears workspace values
- voice sub-dialog: functional setState so a late loadProviders().then() can't
clobber a picker the user opened meanwhile
- ModelManagementSection cancel button honors the busy state
- doc fix (emptied provider keys are kept as empty arrays), plus tests for the
baseUrl-asymmetry and partial-persist paths
* fix(web-shell): address third review round of model management
- DELETE /workspace/models returns a structured partial-persist response
({ code: 'partial_persist_error', committedKeys }) so callers can reconcile
- scrub the deleted model id out of modelFallbacks so no dangling reference
remains; trim padded request fields before matching
- reload workspace settings after a delete so a cleared active model / scrubbed
fallback isn't shown stale
- narrow the workspace-qualified SDK client back to scope: 'workspace' (that
route is workspace-only); drop the now-dead qualified scope ternary
- remove the unused providerKey from RemoveModelResult
* fix(web-shell): address fourth review round of model management
- handleFallbacksConfirm isolates the settings-reload failure from the
save-failed toast (a reload reject no longer looks like a save failure)
- readScopedModelSetting returns only the edited scope's value (no effective
fallback), so the User tab doesn't show/appear-to-clear inherited values
- Add Model button honors the busy state
- widen DaemonSettingUpdateResult.scope to 'workspace' | 'user' to match the
server echo
- tests: workspace-scope owner path, modelFallbacks scrub broadcast,
baseModelId current-match, and a delete target without baseUrl
* fix(web-shell): use theme --error-color for model delete buttons
Replace hardcoded #d64545 with var(--error-color) so the destructive model
buttons match the per-theme error color used across the web-shell (dark
#fc8181 / light #c0362c) instead of a fixed mid-red.
* fix(web-shell): address qwen /review self-review of model management
- surface requiresRestart for modelFallbacks changes: handleFallbacksConfirm
and handleDeleteModel show the restart notice, and DELETE /workspace/models
reports requiresRestart when a committed write targets a restart-required key
- only scrub a deleted model from modelFallbacks when no other provider still
configures the same bare id (fallbacks are bare-id, so a same-id model under
another provider may still want that fallback)
- widen DaemonModelDeleteResult with requiresRestart; add tests for the
keep-fallback case and the requiresRestart response
* fix(web-shell): address fifth review round of model management
Backend (mixed-scope correctness for model deletion):
- Clear the active model selection in every writable scope whose own
selection names the deleted model, comparing against the removed
entry's stored (unsanitized) baseUrl so a credential-bearing URL is
still recognized after the providers status sanitizes it.
- Scrub modelFallbacks in its own owning scope rather than the
modelProviders owner scope; the two are independently scoped.
- removeModelFromProviders now reports removedBaseUrl.
CLI:
- /language ui accepts --project/--global so the settings panel can
persist a UI-language change to the selected scope while still
switching the daemon's live locale.
Web-shell:
- Fast-model picker forwards the selected scope via --project/--global.
- Theme/Language controls display the selected scope's value; Language
persists through the scoped command.
- The model-management "current" badge uses a single-winner,
endpoint-aware match so a bare current id no longer marks every
same-base-id row current.
- Serialize Set current through the shared busy flag; reset the recorded
scope if voice-provider loading rejects.
Tests: mixed-scope route cases, strict-mutation/client-id harness
assertions, SDK setWorkspaceSetting/deleteModel transport tests, a
useDaemonProviders hook test, language scope-flag tests, and dom tests
for the current-badge and fallbacks normalization.
* fix(web-shell): address second qwen /review self-review of model management
- onSubDialog now records the model persist scope per model sub-dialog
(fast/vision/voice/fallbacks) and no longer for the non-model
approvalMode dialog — the reset effect is gated on the dialog/fallback/
auth flags, so it never covers approvalMode and would otherwise leave a
stale scope for a later command-launched picker. (The flagged voice
"scope-reset race" does not actually occur: that same effect gating means
no render between the synchronous scope set and the picker opening
re-runs it — but the scope handling is now explicit per branch.)
- Add an aria-label to the delete-confirm Cancel button so screen readers
can tell which model's confirmation is being cancelled.
Tests:
- Unit tests for getWritableScopes / getOwnKeyScope (trust + per-scope
ownership, incl. explicitly-set falsy values).
- Fast-model User-tab test asserting the /model --fast --global flag.
- Theme scoped-read test: the control shows the selected scope's value,
not the effective merge.
- Fix the useDaemonProviders mock: `current` is a provider-current object,
not a bare id.
* fix(web-shell): fix voice-picker scope race and provider memoization
- Voice model scope race: the voice picker opens asynchronously (after
loadProviders), so recording the persist scope synchronously up front
let it be clobbered — if the user opened and closed another picker while
loadProviders was in flight, the reset effect reset the scope and the
voice model persisted to the wrong scope. Now the scope is captured from
the click and applied together with the open, guarded by a
modelDialogMode ref so it only opens (and sets scope) when no other
surface opened meanwhile. (Vision/fast are synchronous and unaffected.)
- Depend on the stable `reload` fn (extracted as reloadProviders) instead
of the fresh-every-render providersState object, so handleDeleteModel /
handleCloseAuthDialog aren't recreated each render.
- Add role="group" + aria-label to the model-fallbacks option list so
screen readers announce it as a labeled multi-select group (+ test).
* fix(web-shell): address review round on model-management follow-up
Frontend:
- Remove the redundant `data-keyboard-scope` from ModelFallbacksDialog's
inner div — the wrapping DialogShell already provides it, and the extra
scope became the last match in DialogShell's close cleanup, whose
role="dialog" lookup then failed and dropped focus when the dialog
closed while another was stacked.
- Voice picker open-guard now also checks the fallbacks/auth dialog flags
(via refs), matching "no other surface opened meanwhile" so it can't
open on top of a dialog opened while providers were loading.
- Provider group key includes the index (two providers can share an
authType) to avoid duplicate-key reconciliation.
- handleCloseAuthDialog logs a failed provider reload instead of
swallowing it, like the sibling handlers.
- Model-fallbacks options at the max show a `title` explaining the limit
(new localized string).
Backend:
- Split the DELETE /workspace/models baseUrl validation so a too-long
value reports a length error, not "must be a string".
Tests:
- Workspace-scoped language change (`/language ui --project`).
- Security-sensitive key (tools.approvalMode) rejected at user scope.
- baseUrl length-limit rejection.
- Model-fallbacks Cancel → onClose; delete-confirm Cancel path restores
the Delete button; fallbacks accessible grouping already covered.
* fix(web-shell): a11y + robustness follow-ups on model management
- Model-fallbacks max-limit options use aria-disabled instead of the
native disabled attribute so they stay hoverable and can surface the
"limit reached" title (disabled buttons fire no events); the toggle
handler already no-ops at the max. CSS updated to match.
- Inline delete confirmation dismisses on Escape (the conventional
gesture) so keyboard users need not Tab to Cancel.
- scopeToWire throws on an unexpected SettingScope instead of silently
reporting it as 'user'.
- Re-export DaemonWorkspaceProviderCurrent from the webui daemon facade
alongside the sibling provider types.
|