Commit graph

39 commits

Author SHA1 Message Date
qwen-code-dev-bot
c9197a2dce
fix(release): extend standalone runtime download retries (#15673)
* fix(release): extend standalone runtime download retries (#15672)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(release): state retry-budget evidence without claiming the failing leg (#15672)

The v0.26.2-preview.0 archive step aborted and passed on an unchanged re-run, but its job log was never retrievable, so raising the download retry budget from three to five attempts is defence-in-depth, not a confirmed fix for the download leg. Record that in the source comment and drop the same unconfirmed attribution from the retry test.

* fix(release): correct retry-budget comment with attempt-1 log evidence

The comment claimed the v0.26.2-preview.0 archive-step log was not
retrievable and the failing leg unpinned. The job log is available via
the Actions API and shows three consecutive 504s from the GitHub
Releases CDN downloading the bun darwin-x64 runtime, so state the
pinned leg directly.

---------

Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-10-27 11:51:23 +08:00
ChiGao
0539160f35
fix(cli): preserve previews and verify installed OpenTUI distribution (#15655)
Some checks failed
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Lint & Static (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
Managed Agent gate / Select the Managed Agent tier (push) Waiting to run
Managed Agent gate / SDK Java () (push) Blocked by required conditions
Managed Agent gate / Managed Agent gate (push) Blocked by required conditions
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
pnpm Worktree Smoke / Install (macos-latest) (push) Has been cancelled
pnpm Worktree Smoke / Install (ubuntu-latest) (push) Has been cancelled
pnpm Worktree Smoke / Install (windows-latest) (push) Has been cancelled
SDK Java Contract Open PRs / Re-check open PRs against main (push) Has been cancelled
* fix(cli): preserve previews and verify installed OpenTUI distribution

* fix(ci): record installed distribution workflow growth

* fix(ci): record installed distribution matrix budget
2026-10-27 00:23:29 +08:00
ChiGao
32b4234b84
feat(cli): ship private Bun and preserve interpreter boundaries (#14959)
* fix(cli): restore bounded OpenTUI dialogs and add migration gates

* fix(cli): preserve runtime boundaries during standalone updates

Preserve Node/Bun archive flavor and validate actual CLI loading, package
identity, version and prepared installation identity before activation.
Keep Node npm transport and restrict Bun registry lookup to trusted config.

Integrates chiga0's update stack through ce67edaba5,
with isolated fixtures and real Node/Bun runtime regression coverage.

Co-authored-by: gary.gq <gary.gq@alibaba-inc.com>

* fix(cli): preserve OpenTUI input and attachment ownership

* test(cli): bound cancellation test waits and cleanup

* feat(cli): ship private Bun for opt-in npm OpenTUI sessions

* feat(cli): align OpenTUI status lines with Ink

* fix(cli): align OpenTUI editor and cancellation transactions

* fix(tui): harden migration cleanup and evidence after review

* test(tui): fail acceptance on unhandled errors

* fix(cli): validate standalone launchers and registry settings

* docs(cli): clarify consolidated OpenTUI delivery status

* test(cli): isolate system settings in TUI parity fixtures

* test(cli): isolate compile cache in standalone runtime checks

* test(cli): reduce standalone runtime fixture compression cost

* fix(cli): settle OpenTUI cancellation when replay resets a session

* feat(cli): align OpenTUI Vim, history and session output

Reuse the shared editing and history contracts, preserve cancellation ownership, and connect accepted interactive turns to the existing output bridge.

* style(cli): format merged OpenTUI test callbacks

* test(core): control terminal conversion in PTY parking regression

* test(cli): preserve visible input across session rotation

* test(cli): cover shell recall provenance and streaming search selection

* test(cli): await cancellation before releasing held response

* fix(cli): preserve imported history ownership and active search

* docs(cli): synchronize consolidated OpenTUI validation status

* test(cli): await AppContainer configuration cleanup

* test(cli): mock Vim clipboard imports before module loading

* test(cli): check standalone asset roots for npm subpaths

* test(cli): share clipboard mocks across module export views

* test(cli): verify finalized MOD parts and shared JSON projection

* fix(cli): reuse Ink editing history for OpenTUI Unicode input

* test(cli): keep native Vim input callbacks void

* test(cli): preserve Escape boundaries in Unicode Vim fixtures

* test(cli): read the current composer across Vim remounts

* test(cli): align Unicode deletion cursor with Ink oracle

* fix(cli): preserve completion and escape precedence and validate status colors

* fix(cli): use boundary selection for the OpenTUI composer

* fix(cli): preserve attachment navigation with one completion

* test(cli): preserve Shift in the native redo key sequence

* fix(cli): display session titles in the OpenTUI composer border

* fix(cli): discard generated command work after cancellation

* test(cli): align Unicode unit cursor with Ink oracle

* test(cli): model native selection key delegation

* fix(cli): preserve Vim Escape ownership across listeners

* fix(cli): preserve shared Vim command messages in OpenTUI

* test(cli): observe rendered Vim mode during parity checks

* docs(cli): record integrated OpenTUI validation checkpoint

* fix(ci): account for dedicated TUI parity coverage

* fix(cli): preserve collapsed paste payloads across Vim undo

* docs(cli): refresh OpenTUI distribution acceptance status

---------

Co-authored-by: gary.gq <gary.gq@alibaba-inc.com>
2026-10-23 10:27:53 +08:00
ChiGao
97567d5dd7
feat(core): add a real PTY backend for the Bun runtime (#14046)
* feat(core): separate app launcher and JS interpreter runtime boundary

Establishes the runtime boundary the Bun migration (MYWO-8 S1) needs
before workers, hooks, sandbox helpers and the code-mode host spawn
JavaScript children under a future compiled-app flavor: arbitrary-JS
child processes resolve their interpreter through one accessor (package
manifest first, process.execPath fallback) instead of reading
process.execPath directly, Bun detection has a single seam, and the
app-launcher contract is documented alongside the full call-site
inventory in a bilingual design doc. Adds the same-fixture Node/Bun
regression matrix (stdio framing, exit codes, SIGTERM, IPC channel,
--expose-gc, --input-type=commonjs --eval).

* fix(core): anchor interpreter resolution to the trusted install root

Arbitrary-JS child resolution no longer walks upward looking for a
manifest: the only trusted location is the standalone package root —
the parent of the caller's bundle directory, which must hold both a
manifest.json and the standalone entry marker lib/cli-entry.js — so an
unrelated ancestor manifest in npm, source or SDK layouts can never be
read. Declarations are validated before use: runtime must be exactly
'bun' or 'node', and the interpreter must be an existing executable
regular file; any invalid or missing declaration falls back to
process.execPath without throwing (non-string interpreter, directory,
non-executable file, unknown runtime, absent binary, missing marker).

Adds the regression matrix for unrelated ancestor manifests (npm
bundle and deep SDK layouts), object/number/directory interpreters,
unknown runtime, missing and non-executable interpreters, and the
trust anchor; syncs both design-doc language versions with the
validated contract.

* docs(design): complete the migration inventory handoff

Adds the three interpreter-identity call sites the re-review found
missing from the call-site inventory (hook-command-cgroup launcher,
bwrap-execution and landlock-execution allowlist staging), marks the
unwired rows as owned by the S3 migration sweep, and notes platform
applicability: the cgroup/bwrap/landlock rows are Linux-only
mechanisms validated against the Linux targets of the platform
matrix, while the mem0 and pem rows apply on every platform. Both
design-doc language versions are updated; no product or test code
changes.

* feat(core): add a real PTY backend for the Bun runtime

Under Bun, @lydell/node-pty's wrapper pumps the PTY master fd through a
tty.ReadStream, and Bun surfaces the nonblocking fd's EAGAIN as an error
event instead of "no data yet", destroying the stream and closing the
master: onData never fires, resize fails with EBADF, and the shell tool
and web terminal hang. Reproduced on Bun 1.3.14 and 1.4.0.

getPty() now loads a qwen-bun-pty backend under Bun (non-Windows) that
drives the same native addon directly with a main-thread async fs.read
pump: EAGAIN retries on a short timer instead of erroring, EIO/EBADF end
the pump and close the fd, writes queue through an async fs.write with
EAGAIN retries, and exit waits briefly for the drained fd so data is
never truncated behind onExit. The wrapper's observable contract (env
sanitizing, flow-control bytes, encoding, resize validation, IPty
lifecycle) is mirrored, and the pump starts in the constructor so an
unpumped fd can never hold the event loop open.

Packaging stages the node-pty addon for both runtime flavors, since Bun
installs now load it through this backend. The Windows-under-Bun gate
stays honest (explicit load error), and the runtime-boundary design docs
point at the new backend doc for the mechanism and evidence.

Tests: unit coverage for the backend lifecycle (output, write, resize,
kill, pause/resume, flow control, raw encoding), getPty selection under
Bun/Windows, and a cross-runtime regression that runs the same fixture
through the real getPty() seam under both Node and Bun when a bun binary
is present.

* fix(core): keep PTY exit detection alive while paused

The pinned node-pty addon fires its native exit callback only around
master-fd read activity (isolated-probe evidence, identical on Node and
Bun), so the old pause() that stopped the pump lost onExit entirely:
pause then child exit left the master fd open and the host unable to
drain — the same mechanism behind the XOFF-then-cancel FD residue under
handleFlowControl.

pause() now buffers output into a bounded hold (1 MiB) that resume()
flushes, while reads continue so exit detection rides on the pump.
On end of life the hold is discarded for wrapper parity: onExit arrives
exactly once and the fd is released, but resume-after-exit replays
nothing, matching the wrapper's destroyed paused socket. Docs are
synced to the actual main-thread async fs.read pump (the earlier
worker-threads/blocking-read sketch is documented as abandoned), and
regressions cover pause-at-exit, hold overflow, and XOFF-cancel on both
runtimes.

* fix(core): make the Bun PTY pause hold lossless and exits leak-free

Serializes master-fd reads behind an in-flight guard (a resume chaining
into a read that is still outstanding would cross-observe the shared
64 KiB buffer), holds paused output without a cap so nothing is ever
silently dropped, and routes every end-of-life path through one cleanup
that flushes the decoder, stops the retry timer, closes the master fd
and fires onExit exactly once — including the 500 ms fallback, which is
now ref'd so exit delivery cannot depend on a droppable timer. Pins all
of it with byte/line-exact regressions: no-gap pause/resume storms and
a ~1.9 MB flood resumed byte-for-byte, cross-runtime.

* fix(core): cap the Bun PTY pause hold and terminate on overflow

The lossless pause hold could grow without bound while a consumer
stays paused. The hold now copies chunks into fixed 64 KiB blocks
under a per-pty byte ledger (4 MiB default) inside a process-wide
budget (32 MiB total hold, 8 active ptys, 1 MiB write queue), so the
bound holds for any producer chunk pattern.

Reaching a cap terminates that pty (SIGTERM then SIGKILL) and emits
one structured PtyLimitError — never a success result and never a
fabricated native exit; the real signal death lands only in
stats.nativeExitRecord. Shell executions converge the error into
{error, exitCode: null, signal: null}; web terminals mark the session
failed and the /terminal route sends an error control frame followed
by close 4001. Node behavior is unchanged.

Design docs corrected per review: the drip "kernel corruption" claim
is withdrawn, and exit detection wording no longer overstates when
exits are observed.

* fix(core): harden post-termination pty edges per review

Dual-review follow-ups on the bounded hold: resize() joins write() in
treating a terminated/closed pty as a no-op instead of throwing EBADF
from the addon; a read that was in flight when the limit termination
closed the fd no longer delivers or re-charges its late bytes; and a
resume() replay stops at the first data listener that terminates the
pty, so no held bytes flow after the structured error.

* fix(core): detach pty error subscriptions by observed shape, not assumed Disposable

node-pty's Terminal.prototype.on proxies the listener to its master socket
and returns undefined, while an EventEmitter-based pty (the Bun backend)
returns the emitter itself. The registry stored that return value as a
Disposable and called dispose() on release, so every Bun release, natural
exit, or backend failure path threw TypeError from the route/registry and
could take the whole serve process down (server exit 1, client saw 1006
instead of the explicit close).

Store the subscription return as unknown and detach by observed shape: a
real { dispose } is used as-is, otherwise off/removeListener removes the
listener, and undefined (node-pty's actual return) keeps the previous
no-dispose behavior unchanged.

Covered by two registry unit tests on the mocked shapes plus a real-serve
lifecycle probe (release 4004 / exit 4000 / backend over-limit 4001 with
failed-session reconnect replay, server alive throughout) run under Bun
1.3.14, Bun 1.4.0, and Node.

* fix(core): close Bun PTY fds only after in-flight ops settle

Review round on #14046:

- bun-pty closeFd() ran closeSync() while libuv fs.read/fs.write ops could
  still own the master fd, so the recycled descriptor number could take a
  queued write into an unrelated session. fdClosed now flips and the
  process-wide admission slot is released immediately, but the real close
  is deferred until the last outstanding op settles, with witnesses for
  both the read-held and write-held arms.
- A write-side EIO/EBADF (child already gone) is the expected teardown
  race and is no longer reported, matching the wrapper's swallowed EIO;
  other write errors keep their errno code so the entry classifier and
  getPtyLimitMessage can recognize them instead of escaping as an
  uncaught exception. The pump's emit dispatch runs under try/finally so
  a throwing error listener can never strand the pump.
- The web-terminal registry's pty error subscription no longer re-throws:
  a throw there leaves the backend's fs callback as an uncaught exception
  and kills the serve daemon. EIO/EAGAIN races are swallowed and other
  non-limit errors go to the diagnostic sink.
- Failed sessions no longer count against the web-terminal admission cap,
  and release() no longer fabricates a 143/15 exit frame for them.
- processPtyBudget.maxActivePtys is 64 (headroom over the eight-slot
  web-terminal cap so a full house cannot starve the shell tool), with
  the per-pty vs process-wide cap relationship documented.
- ShellExecutionResult.executionMethod declares 'qwen-bun-pty', its
  error-field contract documents resource-limit termination, and the two
  casts that hid the Bun backend name are dropped.
- getPty tests that assert the Bun arm loads now pin a POSIX platform so
  they pass on Windows hosts; docs and the packaging help text are
  aligned (correct smoke-runtime.js path, boundary-doc PTY row now cites
  the bun-pty fixture, node-pty staging applies to both runtimes).

* chore: re-verify S1 boundary test-file merge resolution with recorded evidence

Empty commit: no code change is warranted by the deterministic gate's
test-weakening rejection. The merge ddc7666b9f resolved
packages/core/src/utils/runtime-info.test.ts and
packages/core/src/utils/runtime-boundary.node-bun.test.ts to main's
reviewed versions from #14036 (2eab3cc431), replacing the older
stacked draft copies this branch carried while the prerequisite was
unmerged. Both files are byte-identical to origin/main (empty merge-
base diff: this PR no longer modifies them), and main's versions are
strictly richer (19/16 vs 16/15 expect() calls), so no assertion
coverage is lost. The per-file evidence is recorded in the round's
test-weakening.json; restoring the drafts would revert the reviewed
suites and re-introduce the stacked duplicates the merge correctly
eliminated.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): preserve PTY failure output and bounded settlement

* fix(core): deliver reaped PTY exits on limit trips, replay failed scrollback

Review round on the Bun PTY backend:

- terminateResourceFailure no longer signals a pid it already reaped
  (pendingExit parked): the real exit is delivered and the structured
  limit error reported, instead of SIGTERM/SIGKILL at a recycled pid.
- The failed-reconnect branch of the terminal WS route replays the
  retained scrollback through the backpressure guard before the error
  frame, matching the exited path.
- QWEN_PTY_ACTIVE_LIMIT no longer claims a termination that never
  happened, and acceptedBytes stays a byte count (0 at refusal).
- sanitizeEnv also applies when options.env is omitted, matching the
  wrapper's resolved-env rule.
- The arithmetically unreachable paused-hold-segments cap is deleted.
- Tests: the live-failure route test now reaches a live session; the
  registry limit test is parametrized over both limit codes; the
  paused-exit fixture gains a withheld-tail oracle so it cannot pass
  without a real pause; the bounded-hold tests get afterEach failure
  isolation; the process-wide write-queue budget is exercised; and the
  stale throw-propagation assertion is reconciled with the deliberate
  reportListenerError containment (un-breaks the core test lane).

* docs(core): clarify pending PTY close validation

* docs(core): fix PTY validation checklist indentation

* fix(pty): make resource failures the terminal event

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(cli): raise ps maxBuffer for loaded CI shards

The managed-runtime process probe lists the whole process table with execFileSync. On a loaded CI shard that output exceeds the 1 MiB default and the probe fails with ENOBUFS; raise the cap to 16 MiB.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(core): name the PTY fd-lifetime tests

Point the deferred-close validation entry at the two exact fd-lifetime tests in both design languages.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(cli): release failed terminal replay sessions safely

* test(web-shell): verify failed terminal replay protocol

* test(cli): pin failure listener cleanup on terminal exit

* fix(core): preserve failure precedence across terminal exits

* fix(core): isolate terminal failure notifications and verify release

* fix(core): preserve PTY write and admission failure diagnostics

* test(core): assert PTY cleanup drains every budget

* test(core): verify production PTY refunds start with charged budgets

* fix(core): stop late PTY signals and integrate cleanup refund tests

* test(core): witness PTY subscriber delivery and document native loading

* test(core): reclaim captured PTYs when fd race assertions fail

* test(core): verify exit diagnostics with a real PTY child

* fix(core): avoid signalling reaped PTY leaders during fallback

* test(core): pin paused PTY boundaries and isolate loader probes

* fix(core): verify PTY ownership and target-specific packaging

* fix(core): refuse oversized terminal input before the pty cap

* fix(core): park paused PTY reads at the hold cap instead of killing the tree

Under the Bun backend the shell tool passes handleFlowControl: true, so a
single Ctrl+S (XOFF) byte calls pause(). The pump kept reading into the
paused hold and, once the 4 MiB per-pty (or 32 MiB process-wide) hold was
full, terminated the child process tree with QWEN_PTY_OUTPUT_LIMIT. During
a flood that killed the command about a second after one keystroke.

Size each paused read to the room left in the hold and, once there is
none, stop issuing reads until resume(). The kernel pty buffer then fills
and the child blocks on write, which is the backpressure the node-pty
wrapper's paused socket applies. Nothing is dropped or killed, and
resume() replays the hold and restarts the reads; pty.stats.readParked
exposes the state. QWEN_PTY_OUTPUT_LIMIT and the paused-hold limit kinds
are removed together with the consumer branches that handled them. The
write-queue and admission limits are unchanged.

While parked, a child that dies with output undrained may only finish
exiting on macOS once the master is read again, so the shell tool's
cancel now calls resume() before it signals the process group.

The design docs also get the live-pty cap right (64, not 8) and no longer
describe a block-count cap that the hold never had.

* test(core): run the PTY drain-window witness on Linux only

The real-child test for a write-limit trip after the native exit relies
on a HUP-ignoring grandchild keeping the pty slave open after the session
leader exits. macOS ends the master side right away (a plain-pty probe on
Darwin reads EOF about 70 ms after the leader exits), so the pump closes
the fd before the write and the case failed 3/3 there. Skip it off Linux;
bun-pty-failure-order.test.ts still pins the same guard on every platform
with a mocked native exit.

* fix(core): reap a PTY's whole session on a resource-limit termination

terminateResourceFailure signalled only the leader and the leader's
process group. An interactive shell runs each foreground job in its own
group, so a job that ignores SIGTERM and SIGHUP outlived the termination:
the failure was reported and the budgets refunded while the job kept
running, reparented to init. Reproduced on Linux and macOS.

List what the pty owns with ps while the leader is still unreaped: its
descendants, plus every process in its session (Linux sid) or on its
controlling tty (macOS tdev). Signal each of them as well as the group.
The SIGKILL pass after the grace period lists the tree again. It keeps
an earlier member only while that member's start time still matches, so
a recycled pid is never signalled. It reuses the scope only where the
scope still names the session: macOS drops the tty with the leader, and
Linux keeps the sid unless the pid now leads a new process.

The web terminal registry's killPtyTree already walked the same ps
listing; both now share collectPtyProcessTree.

* test(core): tolerate the pty's CR jitter in paused-flood oracles

macOS's pty driver occasionally inserts an extra CR before LF; a plain script(1) probe reproduces it without this project's code. The paused-flood tests compared the stream byte-for-byte and waited for pausedBytes to equal the logical byte count, so a jittered stream could never satisfy the predicate.

Wait for pausedBytes >= the logical size, derive the process-wide case's expected hold from the observed hold, and compare the resumed stream with runs of CR before LF collapsed. Every line's content, order and count is still asserted, so loss and duplication remain caught.

---------

Co-authored-by: gary.gq <gary.gq@alibaba-inc.com>
Co-authored-by: qwen-code-autofix <qwen-code-autofix@localhost>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com>
Co-authored-by: wenshao <shaojin.wensj@alibaba-inc.com>
2026-10-21 07:14:43 +00:00
qqqys
5f638ae436
feat(generated-ui): bundle Generated UI behind an experimental flag (#14888)
* feat(generated-ui): bundle behind an experimental flag

* fix(generated-ui): preserve runtime admission and settings aliases

* fix(generated-ui): invalidate owning cards after MCP refresh

---------

Co-authored-by: 衍星 <qiuyusheng.qys@alibaba-inc.com>
2026-10-21 11:52:03 +08:00
qqqys
9763580b84
feat(mods): run experimental headless Mod commands (#13843)
Some checks are pending
Qwen Code CI / web-shell E2E Smoke (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java Flyway Open PRs / Re-check open PRs against main (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / Flyway migration version uniqueness (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
SDK Java / Runtime Broker and Managed Agent MariaDB / Java 21 (push) Waiting to run
SDK Java / Hosted process fault gates / MySQL 8.4 / Java 21 (push) Waiting to run
Verify Landlock helper / Native Linux CLI boundary (push) Waiting to run
Verify Landlock helper / Native Windows settings and streams (push) Waiting to run
Verify Landlock helper / arm64 (push) Waiting to run
Verify Landlock helper / x64 (push) Waiting to run
* feat(mods): run experimental headless Mod commands

* fix(mods): preserve marketplace hooks and packaging fixtures

* fix(mods): skip ignored Claude hook paths

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
2026-10-10 16:52:23 +00:00
易良
abcf23a3d9
feat(memory): bundle Mem0 with the main CLI (#12891)
* feat(memory): bundle Mem0 with the main CLI

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* feat(memory): support settings-defined Mem0 credentials

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(memory): preserve bundled Mem0 trust and configuration boundaries

Ignore repository-scoped MCP conflicts, require the genuine bundled runtime for confirmation hooks, and use native artifact paths in packaging assertions. Improve field diagnostics and document existing precedence, scope, and verification limits.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(mem0): close three review Criticals on the bundled Mem0 binding

R1-1 (scripts/tests/package-assets.test.js): the two new multi-segment
`missingArtifact` entries are compared against a message built with
`path.join`, so `mem0/main.js` never matches the `\`-joined Windows path.
Normalise the separator in the assertion; single-segment entries and POSIX
behaviour are unchanged.

R1-2 (packages/cli/src/config/config.ts): the mem0/external-context conflict
guard read the merged all-scopes map, so a `external-context` entry that a
trusted repository contributes through its own `.qwen/settings.json`
(`scope: 'workspace'`) aborted `loadCliConfig` for every operator who
configured `memory.mem0` — before `assembleMcpServers` applied the approval
gate that would have held that server pending anyway. Limit the settings leg
to operator scopes via `isGatedMcpScope`; a gated-scope entry is now
overridden by the built-in binding (topTierMcpServers spreads last), exactly
as a `.mcp.json` entry of the same name always has been, matching the design
doc this PR adds.

R1-28 (packages/cli/src/config/mem0-settings.ts): `bundledMem0Hooks`
identified the bundled server by duck-typing an env-var name plus
`includeTools`, so a repository-authored `external-context` entry reaching
the `/hooks` reload path through `getMcpServers()` was filed under
`systemHooks` — the one hook source folder trust does not gate — rendered as
`[System]` configuration and pointed at a different script than the one the
MCP approval dialog showed. Require identity instead: every file-sourced
entry carries a provenance `scope`, the bundled one never does.

Tests: workspace-scoped override + operator-scoped conflict still throws
(config.test.ts), three scoped-entry rejection rows + an unscoped positive
control (mem0-settings.test.ts), and a control/negative pair on the real
reload path (hooksCommand.test.ts). Each fix was mutation-proved red.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmultfgjtdh

* test(mem0): pin the spawn interpreter, timeout slack and both bound copies

R1-23: nothing in the repo observed `server.command` or `server.timeout`, and
the `memory.mem0.timeoutMs` bounds are declared independently in
mem0-settings.ts (zod, the runtime contract) and settingsSchema.ts (the
settings-dialog copy). Replacing `command: process.execPath` with `'node'`, or
dropping the `+ 5000` slack, left every suite green — a packaged or
GUI-launched install routinely has no `node` on PATH, and the same value gates
`bundledMem0Hooks` and is interpolated as the hook interpreter.

Adds, in mem0-settings.test.ts: `command === process.execPath`,
`config.timeoutMs === 5000` / `server.timeout === 10_000` for the default, an
accept row at the documented ceiling asserting `35_000`, and reject rows for
`0` / `30001` / `1.5`. Adds, in settingsSchema.test.ts, the bound test mirroring
the existing `tools.webSearch.timeoutMs` precedent so the JSON-schema copy is
pinned to the same documented numbers (docs/users/features/mem0.md: "defaults to
5000, between 1 and 30000").

Mutation-proved individually: `process.execPath` -> `'node'` red, `+ 5000`
removed red, `maximum: 30000` -> `60000` red; intact tree green (27 + 61).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmultfgjtdh

* fix(mem0): restore PolarDB direct-import text on retrieval

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(mem0): use the real bundled binding in hook reload control

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(memory): Preserve Mem0 operator and legacy preset semantics

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(memory): Pin Windows Mem0 package artifact errors

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(memory): Retain Mem0 safeguards across reloads

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-30 07:57:11 +00:00
jinye
1a5a74bc35
feat(core): Add Landlock execution fallback (#12278)
* feat(core): Add Landlock execution fallback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(ci): Track Landlock helper workflow size

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(sandbox): Clarify Landlock compatibility limits

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(sandbox): Repair installed Landlock helpers and prompt guidance

Restore missing execute bits only for the bundled helper, anchor asset lookup to the actual source layout, and describe the effective Landlock boundary in the model prompt. Keep portable asset checks on Windows.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-29 03:45:46 +00:00
易良
010941d202
fix(standalone): pin @lydell/node-pty-linux-arm64 and fail release on missing prebuilds (#12649)
* fix(standalone): pin @lydell/node-pty-linux-arm64 and make missing prebuilds fatal at release

The wrapper publishes a linux-arm64 prebuild at the same 1.2.0-beta.10 pin,
but the root and core manifests never declared it, so the linux-arm64
standalone archive and the linux-arm64 Desktop runtime both degraded to
"PTY not available" while every other target shipped a working web
terminal.

With the pin in place every shipped target resolves a prebuild package, so
the release archive build can stop degrading silently: release.yml now
exports QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 next to the existing
audio-capture gate, turning a missing prebuild into a hard failure instead
of another #11872.

Fixes #11872

* fix(standalone): update pnpm lockfile for the linux-arm64 node-pty pin

The two package.json manifests gained @lydell/node-pty-linux-arm64 but
pnpm-lock.yaml was not regenerated, so every CI job failed at the
frozen-lockfile install with ERR_PNPM_OUTDATED_LOCKFILE.

* docs(standalone): align the linux-arm64 PTY guide and tripwire with the pin

Pinning @lydell/node-pty-linux-arm64 makes linux-arm64 a supported PTY target,
but INSTALLATION_GUIDE.md still told users that archive ships without PTY and
reports "PTY not available". CI could not catch the drift because
install-script.test.js pinned that exact sentence as its tripwire, so the guide
and the assertion have to move together.

List all five shipped targets in the guide, retarget the tripwire at the new
sentence, and refresh the matching stale comment in desktop-shell's
test-release.js degrade fixture - that arm now only fires for a target the map
does not know.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(standalone): complete the node-pty linux-arm64 pin in esbuild externals

`create-standalone-package.js` documents that "esbuild.config.js keeps every
node-pty specifier external" so the bundled CLI resolves the platform prebuild
from `lib/node_modules` at runtime. The external list carried five of the six
pinned platform packages and omitted `@lydell/node-pty-linux-arm64` — the exact
platform this PR pins. Nothing imports that specifier statically today, so the
bundle is unaffected; the gap is that the invariant the archive's PTY support
rests on was one entry short of complete.

Probe (esbuild, `--platform=node --format=esm`, entry importing both
specifiers):

- without the entry: `Could not resolve "@lydell/node-pty-linux-arm64"`
- with the entry: builds clean and emits the import left external, matching
  the other five platforms

Also corrects two comments this PR rewrote in the previous round. Both claimed
the desktop degrade arm "fires only for a target the map does not know (a
future platform)". That trigger is unreachable: `desktopTarget()` throws
`Unsupported desktop target` for anything outside its five-entry allowlist and
`NODE_PTY_PREBUILD_PACKAGE` maps exactly those five, so `prebuildPackage` is
always a real name. Verified by running the script:

    $ QWEN_DESKTOP_SKIP_BUILD=1 QWEN_DESKTOP_TARGET=freebsd-x64 \
        node packages/desktop-shell/scripts/prepare-runtime.js
    Error: Unsupported desktop target: freebsd-x64
        at desktopTarget (prepare-runtime.js:351:11)

The reachable trigger is the one the warn string already names and the one
`test-release.js` reproduces four lines below its own comment: a known target
whose prebuild package is unpinned in the source root manifest.

Comment-only apart from the single external entry. prettier --check and
eslint --max-warnings 0 clean on all three files; node --check passes.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(standalone): pin the node-pty release gate and its target map

Closes out three review findings on the node-pty pin. Test- and
comment-only apart from one new export; no production behavior changes.

R1-1 prepare-runtime.js: the nodePtyPackageSpecs() docstring still claimed
its null return "is how an unsupported target degrades". desktopTarget()
throws for anything outside its five-entry allowlist, and
test-release.js:testRuntimeNodePtyTargetMapping() asserts
NODE_PTY_PREBUILD_PACKAGE's keys equal exactly that allowlist, so an
unknown target can never reach it. A null here is a dropped pin. Verified
by re-running that tripwire's extraction against the edited file: both
literals still match and keys == allowlist == the same five targets.

R1-2 create-standalone-package.js: export TARGET_PREBUILD_DIR and assert
every release target maps to a package the manifests actually pin. The
guide sentence this PR adds states that invariant while release.yml:707
makes violating it abort the whole archive step, yet the map had zero test
references. The expectation is derived from readNodePtyPackageSpecs() so
the version list stays single-sourced, and is one-directional because
@lydell/node-pty-win32-arm64 is pinned while no target maps to it.

R1-3 install-script.test.js: the release.yml assertion matched only the
variable name, which also appears in the packager's usage text, so nothing
connected producer to consumer and no test executed the fail() branch at
create-standalone-package.js:578. Tightened to the full wiring line and
added the behavioral twin of the audio-capture pair: gate on with the
addon removed must throw, gate off must still build a PTY-less archive.
packageFakeStandalone() gained an env option for this; spreading
process.env leaves its 15 existing callers unchanged.

Mutation-checked, each mutant applied alone then restored and md5
verified: win-x64 mapped to an unpinned name, gate reader renamed to '2',
gate polarity flipped to !==, and release.yml no longer evaluating to '1'
are each caught by a new or tightened assertion. The polarity flip was
previously undetectable on the PR lane, where the linux-x64 prebuild is
present so the gate is never reached.

Focused: install-script.test.js 130 passed / 11 skipped (141).
prettier --check and eslint --max-warnings 0 clean on all three files.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmufv3et42h

* docs(standalone): state the real node-pty degrade conditions in comments

prepare-runtime.js said the degrade arm fires only "where the repo pins
nothing", but nodePtyPackageSpecs returns null at the first unpinned name of
the pair, so it also fires when the wrapper pin is the one that went missing
while the warning names the prebuild package. Say "either name in the pair"
and note which package the warning names.

create-standalone-package.js justified the non-fatal default with "a developer
archive stays usable on an unmapped future target", which cannot happen:
main() calls copyNativeAddon first and it reads the same TARGET_PREBUILD_DIR
in an unconditional path.join(), so an unmapped target dies with a TypeError
in the audio step before copyNodePtyAddon runs. Replace the rationale with the
case that does reach the warn arm (a target whose prebuild the host install
does not have).

Comment-only; no behaviour change.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmug1iw422v

* test(standalone): make the node-pty gate assertions discriminate

Pin the `Required ` prefix on the fatal-gate regex. copyNodePtyAddon builds
one message and emits it on both paths, so the prefix-less regex was also
satisfied by the degrade warning folded into the child's stderr — the suite
would have reported the gate as covered after the prefix was dropped from
fail(). The sibling clipboard gate assertion already spells the prefix.

Assert QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD structurally on the `Build
Standalone Archives` step env instead of as a whole-file substring, so
commenting the line out or moving it into a neighbouring step's env goes red.
build-standalone-release.js spawns the packager with no env override, so that
step's env is the only route to the gate. The comment defending the substring
pin named a collision with create-standalone-package.js's usage text, which
cannot occur: the searched string is release.yml alone.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmug1iw422v

* test(standalone): cover the node-pty target map and the win-x64 dir name

Two coverage gaps left by the previous round of gate tests:

- The map test looped over TARGET_PREBUILD_DIR itself, so a release target
  with no map entry was never visited, deleting a whole entry merely removed
  an iteration, and an emptied map passed vacuously. Drive the loop from
  TARGETS and assert the lookup before the pin. Still one-directional:
  @lydell/node-pty-win32-arm64 is pinned while no target maps to it.

- Both gate tests reached the packager through packageFakeStandalone, which
  hardcoded --target linux-x64, the one dialect where the prebuild dir name
  equals the target name. Add a win-x64 arm so the 'win-x64' -> 'win32-x64'
  translation in copyNodePtyAddon is covered on every lane rather than only
  on test_windows. Parameterise the staged-modules fixture and the packaging
  helper to make that reachable without a Windows host.

Test-only change; no production code is modified.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmug5t7mp33

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-25 04:38:07 +00:00
qwen-code-dev-bot
0371b40fbd
fix(release): retry transient standalone runtime downloads (#11870) (#11879)
* fix(release): retry transient standalone runtime downloads (#11870)

The v0.23.4 publish died in Build Standalone Archives while the identical
re-run — same commit, same prebuild artifact — passed, so the failure was a
transient external call, not a code defect. That leg downloads SHASUMS256.txt
plus one Node.js archive per target with no timeout and no retry, so a single
nodejs.org blip aborted the whole release.

Bound each download with a 120s fetch timeout and retry up to 3 times with
exponential backoff, verifying the archive checksum inside the loop so a
corrupted download is re-fetched rather than reported once and abandoned.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(release): harden runtime download retries and pin their wiring (#11870)

Address review findings on the retry change. Verify each flavor's
checksum list inside the retry, so a corrupt or truncated SHASUMS256.txt
is re-downloaded instead of poisoning every archive check against a
stale in-memory map; the expected names come from the same
runtimeArchiveName helper the archive leg uses, so the two cannot drift.
Drop the timeoutMs/maxAttempts knobs no caller could set and read the
module constants directly, matching uploadWithRetry. Include error.cause
in retry warnings, since a real undici failure reads only "fetch
failed", and separate the retry notice from the error text. Pin the
retry call sites, backoff schedule, and constant values in tests, and
drop the nodejs.org host attribution from the rationale comment: the
v0.23.4 abort was a transient download failure and the retry is
host-agnostic.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(release): aggregate missing runtime checksums under the display label (#11870)

* test(release): pin bun checksum label and observe retry wiring behaviourally (#11870)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(release): pin archive-leg call site and tolerate comments in wiring pin (#11870)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-21 08:23:58 +00:00
易良
64dd058c9c
ci(pnpm): install with pnpm everywhere and retire package-lock.json (#11859)
* ci(pnpm): install dependencies with pnpm in CI and release

CI tests the graph release ships only if both install it the same way, so
this moves both, keeping every `npm run` script and `npm publish` as they are.

- ci.yml (7), release.yml (3) and release-vscode-companion.yml (2) install
  with `corepack pnpm install --frozen-lockfile`; release keeps
  --ignore-scripts plus its explicit postinstall and generate steps.
- setup-node drops the npm cache and sets package-manager-cache: false,
  which setup-node v5+ needs once package.json declares a pnpm
  packageManager.
- A composite action caches the pnpm store on the three always-hosted jobs;
  the ECS pool keeps its store on local disk.
- version.js no longer lets npm reify node_modules: --no-workspaces-update on
  both `npm version` calls, and a package-lock-only refresh in step 9.
- The notices generator, prepare-package's sharp pin and the standalone
  release's native package specs read the installed tree or pnpm-lock.yaml
  instead of package-lock.json keys.
- New size baselines for the three workflows, which were already over them.

* chore(vscode): regenerate NOTICES from the pnpm-installed tree

Generated in CI (pnpm Worktree Smoke on ubuntu-latest) by the ported
generator. Same entry set as before except the five packages npm and pnpm
resolve differently: fdir 6.5.0, picomatch 4.0.5, mdurl 2.1.0,
@types/node 22.20.1 and mime-db 1.52.0 (form-data's pinned copy). No entry
lacks a license text or repository.

* fix(pnpm): resolve undeclared zod peers to the repo's zod, as npm does

The first CI run on this branch failed the export renderer's size budget:
1,930,015 bytes against 1,930,000, where the same commit built on npm's
tree comes to 1,858,186. Comparing both trees' esbuild metafiles and
prebuilt bundles put the whole difference in web-shell's transcript.js
(+100,903 bytes pre-minify), and the added code is zod 4 plus the
@modelcontextprotocol/ext-apps schemas; the sdk daemon bundle is identical.

web-shell bundles ext-apps, whose zod and MCP SDK are peers, and declares
no zod itself, so pnpm filled the peer with the highest locked zod (4.4.3)
while npm serves the hoisted 3.25.76. Declaring zod 3.25.76 as a root
devDependency makes pnpm resolve every such peer to it
(resolve-peers-from-workspace-root is on by default), which is npm's
hoisting behaviour. mobile-mcp keeps its own zod 4.4.3, as under npm; the
npm tree does not change.

* fix(pnpm): keep npm's root @types/node so root-level typechecks match

The no-AK integration gate typechecks integration-tests/, which takes
Node's types from the root node_modules. npm hoists @types/node 20.19.1
there, because acp-bridge and core pin it; pnpm, with no root declaration,
hoisted 22.20.1, whose spawnSync overloads type process-registry.ts's
stdout as string | NonSharedBuffer and fail TS2322. The same code passes on
every npm-installed PR.

Declaring 20.19.1 as a root devDependency puts npm's copy back where a
root-level tsconfig looks. The npm tree does not change.

* ci(pnpm): install with pnpm in the remaining workflows

The follow-up to switching CI and release: every other workflow that
installs the root workspace now uses the pinned pnpm with
--frozen-lockfile, so no job tests a graph that release does not ship.

- e2e, tui-parity, repo-hygiene, web-shell-visuals, serve-ab, sdk-java,
  windows-runner-smoke, qwen-autofix, the SDK releases, finalize-release,
  sync-release-to-oss, the root install in desktop-release and
  cd-cua-driver, and the sandbox Dockerfile.
- setup-node drops the root npm cache (package-manager-cache: false); hosted
  jobs restore the pnpm store through the shared composite action, and
  qwen-autofix keeps af-012's rule that the persistent pool restores no
  remote cache.
- serve-ab and web-shell-visuals install a separate base checkout that can
  predate pnpm-lock.yaml, so those installs fall back to npm ci there.
- Out of scope, unchanged: packages with their own lockfile (cua-driver's
  TypeScript SDK, desktop-shell, live-host, mobile-mcp), and the triage
  lanes with npm-cache.yml and the npm audit gate, which move with the
  package-lock.json retirement.
- New size baselines for the touched workflows; qwen-autofix stays under
  the 470000-byte gate.

* fix(pnpm): link the workspace packages that root-level code imports

npm links every workspace package into the root node_modules, so
integration-tests/ and scripts/, which are not workspace packages
themselves, can import @qwen-code/* by name. pnpm links a workspace
package only where it is a declared dependency, and the root declared
none, so the no-AK integration gate could not find
@qwen-code/qwen-code-core.

The root now declares the seven packages that root-level code imports at
runtime as file: devDependencies: acp-bridge, channel-base, the CLI,
core, sdk, web-shell and web-templates. npm already links them, so its
tree does not change (letting npm re-resolve the lock reproduces the
same root entry); .pnpmfile.mjs rewrites them to workspace:*, which pnpm
links at the root.

* ci(pnpm): move triage, the store producer and the root audit to pnpm

- qwen-triage's verify and tmux-testing lanes install the PR with
  `corepack pnpm install --frozen-lockfile` from a restored pnpm store.
  The one retry clears node_modules first, because pnpm install, unlike
  npm ci, keeps it.
- npm-cache.yml becomes pnpm-store.yml, keyed on pnpm-lock.yaml.
- security-checks audits the root workspace with `pnpm audit`, which reads
  the lockfile, so the root install step goes. The vendored lockfiles keep
  npm, and the retry recognises pnpm's endpoint error too.
- qwen-autofix drops the hosted-only store cache: its heavy jobs forbid
  local `uses: './...'` actions, and the file sits at the size gate.
- cd-cua-driver: setup-node@v4 has no package-manager-cache input.
- package-assets fixtures install sharp rather than listing it in
  package-lock.json, matching how prepare-package now resolves it.
- AGENTS.md, CONTRIBUTING.md and the npm workspace guide install with pnpm.

* feat(review): install pnpm repos in qwen review's build/test

qwen review installed dependencies only for a repo with package-lock.json
and reported a pnpm repo as unsupported, so reviews of qwen-code would
lose their build and test once its npm lockfile goes.

- lockfileInstaller() picks npm or pnpm from the committed lockfile. With
  both, `packageManager: pnpm@...` chooses pnpm; anything else keeps npm.
- A pnpm repo installs with `corepack pnpm install --frozen-lockfile`
  through the same disk, budget and sandbox path as `npm ci`, and counts
  as installed once node_modules/.modules.yaml exists.
- Scripts still run through `npm run`, and the npm path is unchanged.

* ci(pnpm): retire the root package-lock.json

pnpm-lock.yaml is now the only root lockfile.

- check-lockfile keeps the pnpm integrity check and Playwright parity,
  which now reads pnpm importers and snapshots. The npm-vs-pnpm agreement
  and build-approval checks go: pnpm 11's strictDepBuilds and every CI
  install's --frozen-lockfile enforce what they covered.
- pnpm-lock-freshness.yml goes; it regenerated pnpm-lock.yaml from
  package-lock.json.
- Release: version.js drops its package-lock refresh, the release commit
  stages pnpm-lock.yaml and the SDK release stages no lockfile, so neither
  `git add` fails on the missing file. `npm version` in the SDK and
  mobile-mcp releases passes --no-workspaces-update so it cannot reinstall
  the root with npm.
- cd-mobile-mcp installs from the root pnpm lockfile. setup-node in
  cd-mobile-mcp, qwen-code-pr-review and desktop-release no longer keys an
  npm cache on the root lockfile.
- check-desktop-isolation reads pnpm importers; build.js, build_sandbox.js
  and preflight install with pnpm.
- pnpm-lock.yaml, pnpm-workspace.yaml and .pnpmfile.mjs join the autofix
  supply-chain class and repo-hygiene's deny list; pnpm-lock.yaml joins
  autofix's generated-diff exclusions, sdk-java's path filter and the
  platform-sensitivity manifests.
- .gitignore lists /package-lock.json so an npm install cannot commit it
  back. Tests and docs follow.

* fix(standalone): read the node-pty versions from the pnpm lockfile

* chore(vscode-ide-companion): regenerate NOTICES.txt after the lockfile swap

* chore(vscode-ide-companion): regenerate NOTICES.txt from the pnpm dependency tree

The committed NOTICES.txt was generated from an npm install and does not
match the tree produced by the pnpm install the release pipeline now uses
(659 resolved dependencies, differing @types/node and mime-db versions).
Regenerating with the pinned pnpm install makes the freshness check
reproducible.

* fix(ci): drop the npm cache from the daily dependency audit

actions/setup-node's npm cache keys off the root package-lock.json, which this branch retires, so the scheduled job would fail at Set up Node before the audit ran. No pull_request run reaches this workflow to catch it. The root tree is never installed here either: pnpm audit reads pnpm-lock.yaml directly.

Matches the sibling workflows that already set package-manager-cache: false; scripts/tests/security-workflows.test.js now pins it.

* chore(ci): stop banking main-side drift in the workflow size ratchet

The ratchet exists to turn a workflow's growth into one reviewed line. This branch re-recorded entries for 17 workflows it never touches, several upward by 0.4-2.8 KB - drift that happened on main, banked into the manifest without review.

Restore those to their merge-base values, so .size-baseline changes only for the 22 workflows this branch actually edits. The entries were not needed to go green: ci.yml passes the PR's base commit, so a file that drifted on main and is otherwise untouched earns a warning, not a failure.

* fix(release): initialize standalone state before main

* test(browser-use): read Playwright pin from pnpm lock

* fix(ci): address pnpm migration review blockers

* fix(desktop): stop reading the retired root lockfile

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-20 02:11:25 +00:00
jinye
3486cd8f86
feat(core): Add the bwrap execution foundation (#12067)
* feat(core): add bwrap execution foundation

Extract the internal structured launch, confined worker and trusted completion boundary from the tool sandbox work. Keep runtime policy and CLI activation for subsequent changes, and include worker packaging and standalone Linux acceptance.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(core): bound streamStdout post-exit settle and harden sandbox plumbing

Address PR #12067 review findings:

- settle streaming pipe executions on 'close' or a bounded 1s post-exit
  drain, whichever comes first: settling on 'close' alone wedged the
  result promise whenever an exited shell left a grandchild holding the
  pipes (background shells, ACP channel) — @chiga0 F1, ci-bot R1-2
- keep transport-level stdin EPIPE out of the spawn-error slot unless the
  process left no exit information, so a receipt-confirmed run finalizes
  confirmed and an interrupted run cleans up (ci-bot R1-26); re-key
  sandbox evidence retention on the finalized status (R1-24) and prefer
  the specific transport/spawn error over the generic status error (R1-22)
- allowlist the two new sandbox dist assets for standalone packaging and
  require them at archive build time, with a drift test covering every
  npm files entry (ci-bot R1-1)
- default the payload TERM under --clearenv (R1-4), gate the streaming
  decoder flush on text mode (R1-7), log post-promote settle chain
  failures (R1-25), and pin the caller-signal/pipe-transport contract
  (R1-15), the caller-owned stdin snapshot (R1-31) and both settle-timing
  directions (R1-30) in tests

* fix(core): scope sandbox evidence retention to post-exec uncertainty

Address @wenshao's blocking verification finding on PR #12067: keying
retention on status.state === 'unconfirmed' retained the control/scratch
dir pair for failures that never reached exec — a missing bubblewrap or
payload binary — leaking one pair per invocation and breaking the PR's
own verifier (33/34 on tip vs 34/34 on the reviewed candidate).

- parseBwrapStatus now reports payloadExitObserved when the status wire
  carries a well-formed bwrap exit-code record (bwrap only emits it once
  the payload is past exec), and the relay attests payloadExitObserved:
  false on spawn/transport failure
- the finalizer retains only when the payload may genuinely have run:
  the receipt attests an exit record, or the receipt file exists but is
  unreadable (relay died mid-payload); a missing receipt means the relay
  died before the spawn call, and an attested no-exec receipt means
  setup failed before exec — both clean up
- sandboxStatusError distinguishes the attested no-exec case with a
  "did not run (setup failed before execution)" message (N4)
- design docs record the retention scoping and the drain-window abort
  nuance (N2); PR body now states the bounded-drain trade-off (N1)

Verified: core 204/204, tsc/eslint clean, and the relay probed against
real processes — missing bwrap and exec failure now produce no-exec
receipts (cleanup), an uncorrelated exit record retains, and the
confirmed path is byte-identical.

* fix(core): keep unknown sandbox receipts from attesting non-execution

Address @wenshao's round-2 findings on PR #12067:

- the no-exec attestation was being manufactured from absence of
  evidence at two sites: the finalizer coerced a receipt without
  payloadExitObserved into an explicit false, and the relay attested
  false for every `failed` cause although only a bwrap spawn error
  proves the payload never ran (a status-stream transport error or a
  16KiB wire overflow can happen after the payload executed). Both now
  keep undefined as unknown: only a genuine spawn error or a wire with
  no bwrap exit-code record attests no-exec.
- retention follows: clean up only on a positive no-exec attestation or
  a missing receipt file (the relay creates it O_EXCL before spawning
  bwrap, so absence proves pre-spawn death); unknown retains. The
  fail-safe direction for a retry-deciding caller is restored — an
  oversized or empty wire with the payload having run now retains and
  reads "may have run" instead of "did not run".
- the no-exec message no longer points only at bubblewrap; it also
  covers a missing payload binary.
- verify.mjs: the mount-setup-failure receipt assertion now expects the
  payloadExitObserved attestation (the one shipped deepEqual on the
  receipt object).
- bwrap-status tests now discriminate all three attestation states
  strictly (absent vs false vs true) instead of applying the same
  coercion the code did.

Verified: core 204/204, tsc/eslint clean, relay probed against real
processes for six receipt shapes — spawn failure and child-pid-only
wire attest false, mismatched exit record attests true, empty and
oversized wires stay unattested, success control confirmed.

* fix(core): harden bwrap execution foundation

Address the remaining execution-foundation review findings with deterministic file-worker framing, separate worker diagnostics, scratch-root admission, CI unit coverage, and stronger Linux acceptance evidence.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(core): Make bwrap verifier portable

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-19 13:54:30 +00:00
Shaojin Wen
907667bbd6
feat(core): add container execution for subagents (#11711)
* feat(core): add container execution for subagents

* fix: address container execution review and CI failures

* test(cli): account for relaunch environment provenance

* test: include execution worker in package asset fixtures

* fix(core): harden container execution boundaries and lifecycle

* fix(core): isolate container cancellation and bound cleanup

* fix(core): protect container roots and cache recovery

* fix(core): report container startup failures and waits

* fix(core): reject unsupported teams and arena before setup

* fix(core): keep root git metadata masked across worker startup

* fix(core): reclaim owned git mount points after worker cleanup

* fix: preserve container policy and retry failed cleanup

* ci: refresh PR head and rerun checks

* fix(core): retain cleanup ownership after container startup failure

* fix: fail closed on container policy refusals

* fix(agent): close container execution review gaps
2026-09-18 08:25:17 +00:00
易良
77f1f93d23
fix(core): carry the PTY load reason on the call, not in module state (#11923)
Six follow-ups left on main by #11881, tracked in #11898.

The reason a PTY load failed lived in a module-global that every getPty()
call cleared on entry and only wrote after two `await import()`
suspensions, so a second call in flight could drop it or report its own
cause for someone else's failure. WebTerminalRegistry.create reads it
after awaiting getPty(), and ShellExecutionService.execute calls the same
function for every PTY-backed shell command - its abort path leaves a call
in flight. Measured with the real registry and the real getPty, both
backends made unresolvable: 50/50 kept the cause with no other caller, and
6/50, 9/50 and 5/50 lost it across three runs of a competing two-call
chain, leaving the browser with the prefix-only message. loadPty() now
returns { impl, loadError } so the reason travels on its own call;
getPty() stays `(await loadPty()).impl`, still resolving null and still
typed PtyImplementation for ShellExecutionService.

The Bun arm recorded "the Bun runtime has no PTY backend", which the
registry then prefixed with an independent claim that no backend was
loadable. Bun can load @lydell/node-pty and is deliberately not asked to,
so the reason now names the runtime switch instead of a backend the user
would go and install. That arm still returns rather than throwing, which
create-standalone-package.js relies on when it skips copyNodePtyAddon for
--runtime bun.

getPty.test.ts now pins the mechanism: per-call delivery across an
interleaving call, the two-failure join, no reuse of an earlier reason,
and the Bun reason. Before this, each of the three reason-recording
statements could be deleted with getPty.test.ts, web-terminal-registry
and shellExecutionService all green (3 files / 197 tests, measured).

copyNodePtyAddon gains a filter that drops .pdb files. The win32-x64
prebuild package ships 10,780,672 B of them: 86% of its 12,485,696 B
prebuild payload and ~2.05 MiB of the compressed win-x64 archive, and
nothing reads them at runtime. install-script.test.js stages a fake
prebuild holding a .pdb beside the .node, runs the real packaging script
and asserts on the extracted archive.

INSTALLATION_GUIDE.md said no @lydell/node-pty-linux-arm64 package is
published. It is, at 1.2.0-beta.10 - the version this repo pins for its
five siblings - and package-lock.json already resolves it; the gap is the
missing pin, so the sentence now says that. The assertion beside it
(`toContain('linux-arm64')`) also matched the release-artifact list and so
could not fail; it now pins the reworded sentence.


Patrol-Run: qwen-issue-patrol/jmu29p3id5x

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-15 12:46:37 +00:00
易良
10dafaf796
fix(standalone): bundle @lydell/node-pty prebuilds so the web terminal works (#11881)
* fix(standalone): bundle @lydell/node-pty prebuilds so the web terminal works

The standalone archive declares six @lydell/node-pty* optionalDependencies
but never ships them: esbuild keeps every node-pty specifier external, and
create-standalone-package.js rebuilt lib/node_modules with only the
audio-capture and clipboard addons. Every web terminal creation in a
standalone install therefore failed with a bare "PTY not available".

- create-standalone-package.js: add copyNodePtyAddon, copying the
  @lydell/node-pty wrapper plus the target's own platform prebuild package
  (derived from TARGET_PREBUILD_DIR) into lib/node_modules. Node runtime
  only (getPty() returns null under Bun), warn-and-degrade locally, with
  QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1 as the opt-in fatal gate.
- build-standalone-release.js: stage the pinned @lydell/node-pty* packages
  alongside the clipboard ones in --native-modules-dir, since release
  packaging cross-builds all targets from a single host. The spec list
  derives from the root manifest so a future linux-arm64 pin is picked up
  automatically.
- web-terminal-registry: split the two identical "PTY not available" arms
  into messages that say which failure occurred and on which platform/arch.

Refs #11872

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-issue-patrol/jmu1djouw4l

* fix(core,standalone): name the real PTY load failure instead of claiming no module was found

R1-1: the copyNodePtyAddon comment claimed release packaging sets
QWEN_STANDALONE_REQUIRE_NODE_PTY_PREBUILD=1. Nothing does - release.yml
exports only QWEN_STANDALONE_REQUIRE_AUDIO_CAPTURE_PREBUILD for the
archive build - so the comment now says the gate is opt-in, that nothing
sets it, and why it cannot be wired unconditionally while linux-arm64
has no pinned platform package.

R1-2: getPty() collapses "absent" and "present but failed to load" into
null, so the reachable arm asserted a cause that is false whenever the
wrapper is installed but its prebuild cannot be dlopen'd (#11872's own
signature-refused case). getPty() keeps RESOLVING null, because
ShellExecutionService turns a rejection into a thrown error and loses
the graceful childProcessFallback, but it now records the reasons and
the registry reports them instead of claiming no backend module exists.

R1-3: the installation guide still sent PTY users to --method npm; it now
names the bundled @lydell/node-pty packages for the four targets that
have a published prebuild, and the surviving linux-arm64 gap. The guide
test pins both directions.

R1-5: the --native-modules-dir help text now documents its second
consumer and its different failure polarity.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmu1r4dew54

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-15 01:34:58 +00:00
Dragon
4ec9f63d0a
feat(core): add CodeModeOnly tool execution (#10607)
* feat(core): add CodeModeOnly tool execution

* fix(core): preserve CodeModeOnly execution semantics

* fix(core): drop the tool_search reminder in CodeModeOnly

CodeModeOnly hides `tool_search` and binds every deferred tool — schema
included — into the `exec` description, so progressive discovery has
nothing left to do. The session prelude still announced those tools as
"reachable via `tool_search`", pointing the model at a tool that is
never declared and re-billing the text on every cached prefix. Worse,
tools already callable as `tools.web_fetch(...)` looked gated behind a
lookup step that does not exist.

The subagent and fork-resume callers already opted out, but the
main-session prelude sites did not, so the gate now lives where the
prelude is built rather than in each caller.

* chore: refresh settings schema

* chore: refresh companion notices

* fix: register exec tool metadata

* fix(cli): localize exec tool display name

* fix(core): stop offering ToolSearch lookups in CodeModeOnly

CodeModeOnly hides tool_search and binds every deferred schema into the
exec description, so text that tells the model to look a tool up
describes a surface it cannot reach. Return an empty deferred summary
from the registry itself - this also covers the fork-resume reminder
that bypassed the earlier call-site gate - and point the image zoom
hint at tools.zoom_image.

* feat(core): teach the system prompt CodeModeOnly's tool surface

In CodeModeOnly the model can only call exec, so the tool guidance and the
worked examples described a surface it does not have: bare tool names it
cannot call, an instruction to issue several tool calls in one response, and
examples in a direct tool-call syntax that does not exist there.

Give "Using Your Tools" a code-mode branch that routes every tool through
tools.<name>, states which tools are direct controls instead, and replaces
multi-call parallelism with batching inside one exec program. Swap the four
model-family example sets for one shared exec set, since the syntax
differences they exist for do not apply. The mechanics of exec itself stay in
its tool description; the prompt carries only policy.

* test: include code mode host in standalone fixture

* fix(core): return code mode media as multimodal output

* feat(core): support image tool results in code mode

* fix(core): carry deferred tool schemas in the exec description

CodeModeOnly hides tool_search and never surfaces a nested call as a history
functionCall, so a signature collapsed to Record<string, unknown> could never
be filled in later: parameter names were reachable only by guessing, or by
reading them off validation errors one at a time. Deferred tools keep their
registry state; only the generated signature gains the schema.

* fix(node-repl): restore the bin exec bit after rebuild

npm chmods dist/index.js when it links the bin at install time, but the
build deletes that exact file and tsc re-emits it as 0644. After any
rebuild node_modules/.bin/node-repl-mcp therefore points at a
non-executable target and spawning it fails with EACCES (exit 126)
before the shebang is ever read.

OR the exec bits into the emitted mode instead of setting 0o755, so a
restrictive umask is preserved rather than widened.

* fix(cli): pin patched Ink version

* feat(cli): add collapsible tool call details

* fix(dev): use CLI tsconfig outside repository

* fix(cli): preserve expanded tool details after completion

* fix(cli): fail headless runs on model API errors

* fix(core): expose shell timeout bounds in code mode

* feat(core): add code mode timeout helpers

* feat(cli): expose code mode in settings

* fix(mcp): preserve structured results for model and exec consumers

* fix(core): preserve code mode output and truncate inline

* feat(core): port scoped context tools to code mode

* fix(build): synchronize pnpm lockfile and capability docs

* fix(core): recover from rejected Responses encrypted reasoning
2026-09-11 07:40:35 +00:00
易良
11e629b3a1
perf(export): split the transcript renderer's embedded CSS into a versioned asset (#11485)
* perf(export): split the transcript renderer's embedded CSS into a versioned asset

The export renderer carried the web-shell component stylesheet as a ~2.3 MB
string literal, so every reader parsed and compiled 4.1 MB of JS (56% of it dead
CSS) before a transcript could render. Lift that literal out at export build
time into a version-pinned, SRI-protected export-transcript-document.css served
from unpkg and loaded via a nonce-bearing <link>, dropping the renderer JS to
~1.83 MB.

The transform is an esbuild onLoad plugin in the web-templates export build that
strips the injected CSS constant from web-shell's dist/transcript.js; web-shell
source and runtime behavior are untouched. The document's fail-closed load-error
path is extended to the stylesheet so a missing CSS asset fails the same way as
a missing renderer.

* fix(export): match the transcript CSS entry on Windows paths too

esbuild hands plugin callbacks the platform-native absolute path, so the
extract-transcript-css `onLoad` filter never matched on Windows: the callback
did not run, `extractedTranscriptCss.css` stayed undefined, and the mandatory
extraction guard below aborted the build. That build is not platform-gated —
`scripts/prepare.js` runs it from `prepare`, so `npm ci` itself would fail on
every Windows contributor and on the windows-latest legs of test_windows and
desktop-release.

Widen the separator to `[\\/]`, keeping the `transcript\.js$` tail so the
barred `web-shell/dist/index.js` package root still does not match. The filter
moves to transcript-css-entry.mjs because build.mjs is a top-level-await script
with no harness — the same reason scripts/sdk-node-exporter-stub.js exists — so
scripts/tests/transcript-css-entry-filter.test.js can pin both separators.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtufhms2u4

* fix(export): latch a stylesheet failure ahead of the parser block

The `<link id="transcript-stylesheet">` sits in `<head>` while the `window`
error listener that catches its failure is registered by an inline script in
`<body>`. Chromium parser-blocks that script on the pending stylesheet, so when
the CSS failure settles first the error event is dispatched with no listener to
receive it: nothing marks the render as failed, both renderComplete guards in
document-main.tsx pass, React mounts the transcript without any of the
component CSS, and the requestAnimationFrame stamps
`data-render-complete="true"`. The reviewer measured this fail-open above
roughly 2.1 MB of document HTML (272 of the 1,000 permitted blocks) for a 404,
an SRI rejection, a truncated body and a destroyed socket alike, and fail-closed
for a *late* failure — so size, not failure kind, decides it.

Latch the failure in `<head>` before the `<link>` is parsed and act on the latch
from the existing body IIFE. The head script only records: `showLoadError()`
writes `document.body.dataset` and `#app`, neither of which exists while the
parser is still in `<head>`. It carries `nonce="__EXPORT_NONCE__"` because the
document CSP allows no inline script, which is safe — `formatters/html.ts:53`
replaces every occurrence. The listener is capture-phase because resource error
events do not bubble.

Not the `link.sheet === null` variant: the reviewer measured `sheet` non-null
for a 404, a truncated body and a destroyed socket, so it only detects SRI
rejection.

scripts/tests/export-transcript-document-template.test.js pins the position,
the nonce, the capture phase and the record-only shape; all five cases go red
against the unpatched template. The behavioural witness (real Chromium, large
document, instant CSS abort) belongs to the playwright transcript gate, which
is out of budget on this host.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtufhms2u4

* fix(scripts): name the missing export renderer assets, pin the CSS gate

The bundle copy became all-or-nothing over two artifacts but its `else` warning
still named only the renderer, so the one new way to reach that branch — a tree
built before the split, then `npm run bundle`d without rebuilding web-templates,
which has the JS and no CSS — told the operator to go looking for a
`export-transcript-document.js` that was sitting right there, and silently
discarded it. List the paths that are actually absent, matching the sibling Web
Shell warning twenty lines above. Stays warn-and-skip: prepare-package.js is the
release gate.

Also pin that release gate. Every fixture that reached `preparePackage` staged
`dist/export-transcript-document.css` unconditionally, so deleting the new
required-path entry left the whole test:scripts lane green; a release built with
`npm ci --ignore-scripts` would then publish documents whose stylesheet 404s on
unpkg for that version. `verifyBundleArtifacts` reports through console.error +
process.exit(1) rather than a throw, so the new case stubs exit instead of
copying the audio-capture sibling's `toThrow` idiom.

Both cases were flip-checked: restoring the old warning text, and deleting the
CSS line from prepare-package.js, each turn their case red.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtufhms2u4

* fix(export): pin the stylesheet-failure id contract and sync the design docs

The <head> latch, the body listener and the <link> each spell
'transcript-stylesheet' independently and nothing compared them, so renaming
either listener's id left the whole suite green while the latch recorded
nothing - reinstating the fail-open the latch was added to close. Derive the id
from the <link> and assert both listeners compare against it. Verified red under
both mutations: latch id -> 'transcript-renderer' (1 failed | 5 passed), and the
mirror with the body listener's id wrong and the latch intact (same).

Both design docs still specified the two shapes the previous round replaced: the
forward-slash-only onLoad filter that never matches on Windows, and the
body-listener-only fail-closed extension. Section 1 now quotes the shipped
TRANSCRIPT_CSS_ENTRY_FILTER and names transcript-css-entry.mjs, section 2
describes the <head> latch (position, nonce, capture phase, record-only),
section 3 names the module-level render guard, and "Files affected" lists the
three omitted files. EN and zh-CN are updated in the same commit.

Also correct the shape-guard comment in build.mjs: the document nonces every
<style> created through document.createElement, so the CSP would not block an
un-stripped duplicate, and a 367-byte regrowth stays inside both byte budgets.
That throw is the only guard on the duplicate-injection path.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtuo29vduf

* fix(export): close out transcript CSS review comments

* fix(export): tighten transcript CSS closeout

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-10 07:15:32 +00:00
ChiGao
e09c461a92
fix(cli): restore OpenTUI banner, footer, and loading states; fix the OSS mirror flavor (#11437)
Some checks failed
Qwen Code CI / Integration Tests (no-AK, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Integration Tests (CLI, No Sandbox) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (ubuntu-22.04) (push) Blocked by required conditions
Qwen Code CI / Desktop Shell (windows-2022) (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 1/2 (push) Blocked by required conditions
E2E Tests / E2E Test - macOS - shard 2/2 (push) Blocked by required conditions
E2E Tests / Build for E2E (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/1 (push) Blocked by required conditions
E2E Tests / E2E Interactive - OpenTUI renderer (bun) (push) Blocked by required conditions
E2E Tests / channel-plugin E2E (nightly) (push) Blocked by required conditions
pnpm Worktree Smoke / Install (macos-latest) (push) Waiting to run
pnpm Worktree Smoke / Install (ubuntu-latest) (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/1 (push) Blocked by required conditions
E2E Tests / cron-interactive E2E (nightly) (push) Blocked by required conditions
E2E Tests / web-shell Browser Regression (push) Waiting to run
npm cache producer / Save npm cache (push) Waiting to run
pnpm Worktree Smoke / Install (windows-latest) (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Update ECS Runner Qwen / Resolve version (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-1 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-2 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-3 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-4 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-5 (push) Has been cancelled
Update ECS Runner Qwen / Report a stale fleet (push) Has been cancelled
* fix(cli): restore banner, footer, and loading states in the OpenTUI shell

The batched OpenTUI migration restructured the original single-file backend
into modules and dropped three user-visible pieces of the chrome along the
way: the startup banner, the bottom status line, and the responding
spinner. The OpenTUI renderer therefore booted into a screen that looked
nothing like the ink one it was meant to replace.

Rebuild all three as their own modules and mount them in the app shell,
following the ink layout: banner at the top, loading indicator above the
composer, status line at the bottom.

Two defects carried over from the original implementation are fixed rather
than copied. The context-usage label was missing its percent sign, and the
approval-mode colour check compared against a label that never matched, so
the auto-edit mode fell through to the default colour.

The context-file count is intentionally not restored: the shell no longer
tracks it and the current ink footer dropped it too.

* fix(cli): tick the OpenTUI spinner at the rate ink uses

The restored spinner ran at 120ms, carried over from the pre-batch
implementation, while the ink responding spinner it mirrors runs the same
frame set at 80ms and the existing OpenTUI compaction spinner does too. The
same animation therefore looked visibly slower under one renderer.

Name the interval and set it to the shared rate. The frame set itself is
unchanged.

* fix(release): sync the OSS mirror by what the tag shipped, not by a variable

The workflow that mirrors a release to Aliyun OSS decided whether to expect
the bun/OpenTUI preview archives from a repository variable. A repository
variable describes the default branch, while the checkout and the downloaded
assets come from the tag being synced, and the workflow can be re-dispatched
for any tag. Re-syncing a tag cut before the preview flavor existed therefore
asked that tag's own verify script for archives the release never shipped:
v0.22.3 fails because its script does not know the flag at all, and v0.23.0
fails on a missing checksum entry.

Derive the flag once from the archives actually downloaded and let all three
consumers read it, so the mirror follows the release instead of the branch.

Two related gaps in the same gate are closed here. The test covering it
asserted only the variable name, so inverting the comparison and silently
changing the flavor's default polarity stayed green; it now pins the
operator. And the variable appeared in no documentation, so an operator had
no way to learn that leaving it unset is what keeps the flavor off.

* fix(cli): source the OpenTUI chrome from main's shared helpers

The restored banner, footer, and responding indicator had grown private
copies of mappings main already owns, so the two renderers could drift.
Route the approval-mode label through formatApprovalModeName, extract the
context-usage format and version-label rules into ink-free helpers both
trees consume, and take the spinner frames and loading phrases from the
shared constants and cycler.

Also truncate the footer rows and the indicator phrase to the terminal
width so a long branch, path, or phrase cannot wrap the chrome mid-turn,
and read typed Config accessors in the banner instead of structural
casts and a manual package.json read.
2026-09-09 09:26:07 +00:00
易良
078b924989
refactor(ci): extract release workflow scripts (#11165)
* refactor(ci): extract release workflow scripts

* test(ci): pin release-note labeling to the extracted step script

release.yml no longer inlines the auto-labeling body: the publish step now
just dispatches to run-release-step.sh, which owns the git rev-list
enumeration and the classify-release-notes.mjs call. The wiring test kept
asserting those four strings against the step's run block, so it failed on
the dispatcher path alone.

Read run-release-step.sh and assert the labeling body there, and assert the
dispatch from release.yml. The workflow-level guarantees (step name,
continue-on-error, GITHUB_TOKEN env, issues/pull-requests write permissions)
and the .github/release.yml exclusion entry are unchanged.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtpmlcodmz

* test(ci): repoint release assertions at the extracted step script

The extraction moved the GitHub Release notes, standalone archive, and
release-failure autofix label logic out of .github/workflows/release.yml
into .github/scripts/run-release-step.sh, but three consumer suites still
pinned the moved text in the YAML, so `npm run test:scripts` was red and
the release workflow's fail-closed `quality` gate would refuse to publish.

Read the step script in ai-release-notes-workflow, install-script, and
qwen-autofix-workflow and assert the moved strings there with their new
lowercase locals (`notes_args`/`notes_file`), keeping on release.yml only
what genuinely stayed in it. Restore the "Safe to auto-apply approval"
rationale above both AUTOFIX_APPROVED_LABEL writes in the notify-failure
arm; it is the only recorded justification for the workflow granting
itself autonomous autofix approval.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtq0vr74ni

* test(ci): derive the ECS wipe subject set from the runner, not the wipe

Selecting checkout jobs by "has a Restore workspace ownership step" made
the subject set a function of the property under test, so a job added to
the ecs-qwen-hk4-host pool with an actions/checkout and no wipe was
filtered out before any assertion ran. Key the exemption on the pool
marker instead, and pin that every pool-routed job carries the wipe
whether or not it checks out.

The hosted label cannot be the discriminator: every pool-routed runs-on
expression names 'ubuntu-latest' as its fallback branch.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtq0vr74ni

* test(ci): re-scope the release-creation token pin to its own step

The workflow-wide toContain was satisfied by the second CI_BOT_PAT
occurrence on "Trigger ECS runner qwen update", so flipping the token on
"Create GitHub Release and Tag" to github.token left every lane green.
Restore the step scoping and the deleted rationale comment in release.yml.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2

* fix(ci): fail the release step runner closed on pipeline legs

run-release-step.sh set bare `set -e` while both sibling runners set
pipefail. In notify-failure a connection-level gh failure inside
`gh issue list ... | jq -c ...` leaves jq exiting 0 on empty input, so the
empty result took the create-a-new-issue branch and filed a duplicate
autofix/approved release-failure issue past all three reuse guards.

Pin the flag next to the existing sibling pin and add a behavioural row for
the notify-failure arm with an unreachable gh.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2

* test(ci): relocate the release timeout value-format contract

This PR condensed the nine-line release.yml lane comment to "values are
minutes" and relocated the format half nowhere: grep for the deleted claims
(expression error / free text / truthy / leading number) found no copy under
.github/, scripts/tests/ or docs/. Put the contract beside the assertions that
already pin the three tunable lanes' expressions, since release.yml is
size-pinned twice.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2

* docs(ci): scope the workflow_sha claim to the extracted helpers

The design doc said the trusted checkout "prevents an operator-selected
release ref from supplying code that receives release credentials". Two paths
at HEAD contradict that reading: resolve-version executes the ref's
scripts/get-release-version.js under the job token (run-release-step.sh:84),
and push-release-branch commits with core.hooksPath .husky (:116, :130) in a
step whose env carries CI_BOT_PAT (release.yml:659). Both predate this
extraction, so name them as residual paths rather than as prevented ones.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtqfvvmto2

* fix(ci): format the release-version test to satisfy the Prettier gate

`Lint & Static` failed at the `Run Prettier` step: a continuation line in
the `assert-unreleased` format-gate test carried four extra spaces.

* fix(ci): exclude nested .git dirs from the docker build context

The extraction added a `.release-workflow/` checkout to `integration_docker`
between the sandbox build's reset and the build itself, and `.dockerignore`
listed `.git` only in its root-anchored form. A depth-1 sparse checkout of
the pinned ref is 1.1M of worktree over 51M of git objects, so every
uncached sandbox image baked those objects into a discarded builder layer on
the shared pool this job is routed to. `node_modules` and `dist` already
carry both forms.

* fix(release): close four gaps the script extraction left behind

R1-15: the push-time guard returned exit 2 for a malformed version, and
run-release-step.sh treats 2 as a transient probe failure — so a refusal
that can never succeed was retried three times over ~45s and logged as a
connectivity problem. Malformed versions now exit 4, which the retry loop
breaks on immediately and which is not the exit-3 version_refusal marker.

R1-16: the extraction moved the guard behind `.release-workflow/` so the
decision comes from the workflow-pinned SHA, but left a working
`--assert-unreleased` dispatch in the ref-supplied get-release-version.js.
A future step using that spelling would load the selected ref's guard.
Removed it, and re-pointed its five tests at the real entry point.

R1-13: assert-release-version.mjs ships alone under release.yml's
sparse-checkout set, so it cannot import release-helpers.js — the
duplicated isExpectedMissingGitHubRelease is deliberate. Documented that
at both sites and pinned the two bodies identical, so widening only the
guard's copy (which would read a rate-limited probe as "release absent")
goes red.

R1-6: restored the notify-failure arm's rationale, matching the condensed
comments the other five arms kept: the exact-title re-check that stops a
v0.18.1 failure reusing a v0.18.10 issue, the bot-author preference, and
the re-query that keeps autofix off a maintainer-owned issue.

* fix(release): re-pin the trusted runner before every step that runs it

R1-10: `Build Bundle and Prepare Package`, `Build Standalone Archives` and
`Verify Standalone Archives` executed `.release-workflow/.../run-release-step.sh`
from a tree that selected-ref npm code had already run over — `build-package`
for the first two, and `npm publish`'s own lifecycle scripts for the third,
inside the job that holds `id-token: 'write'`. At the merge base these were
inline `run:` bodies sourced from the workflow file, immune to anything
written into the working tree, so the exposure is introduced by the
extraction. Each now gets the reset-and-re-checkout pair the design doc
already promises for credential-bearing steps.

The guarding test hand-listed six protected step names, so it could not fail
for the three it omitted. It now sweeps every step whose `run` mentions
`.release-workflow/` (17 today) and separately pins that each re-checkout is
preceded by a reset.

R1-5: nothing tied the publish allowlist to the guard's `PUBLISHED_PACKAGES`
except a comment pointing at publish steps this PR deleted from release.yml.
A channel added to the loop but not the array would ship unprobed, so a retry
of a partial release reports "unreleased" and force-pushes over the tip the
shipped package anchors to. The two sets are now pinned against each other,
and the stale comment points at the real pin.

The three inserted step pairs grow release.yml by 294 bytes (well inside the
4096-byte allowance); the ratchet baseline and the PR description move with
it rather than letting the recorded number drift.

* test(release): pin the flags, arms and modes the extraction left unwitnessed

R1-19: the arm's only executable probe stubbed npm with a script that
recorded `$PWD` and discarded every argument, so deleting `--tag=${NPM_TAG}`
stayed green while `npm publish` would fall back to the `latest` dist-tag —
handing the 21:00 UTC nightly to every end-user install and to the ECS fleet
updater. The stub now records the argv and each publish is checked for
`--access public` and the dist-tag.

R2-3: `resolve-version` was the one arm no test executed or text-pinned;
flipping `--type=preview` to `--type=stable` survived all 2233 scripts tests,
while at runtime the stable path ignores `preview_version_override` and would
publish an operator's manual preview as a stable release. Two execution tests
now cover the preview mapping and the malformed-version rejection; both were
run against the real arm before pushing.

R1-18: release.yml invokes these scripts by bare path, so the executable bit
is load-bearing, but every test invokes them as `bash <script>`, which
ignores the mode — and the workflow triggers only on schedule/dispatch, so no
pull-request lane runs the bare-path form. A mode-normalizing commit would
keep the suite green and kill `set-flags` with exit 126. The recorded git
mode is now pinned for all three scripts.

* test(release): pin the trusted prefix, the fork gate and the real version formats

R1-3: every arm pin elsewhere is a substring match that a bare
`.github/scripts/run-release-step.sh` invocation would satisfy just as well,
so nothing pinned the mechanism the extraction exists for. All 17 step
invocations are now required to carry the `.release-workflow/` prefix and
forbidden from naming the release-ref copy.

R1-9: `PUBLISH_AUDIO_CAPTURE` replaced a deleted step-level
`if: github.repository == 'QwenLM/qwen-code'` gate and, unlike its
`PUBLISH_EXTERNAL_CONTEXT_MEM0` sibling, had neither its gate expression nor
its true branch pinned — a fork running this workflow would publish
@qwen-code/audio-capture. Both halves are pinned now.

R1-14: the format gate is the guard's only genuinely new behaviour and its
accept path was exercised solely with '1.2.3', while the two scheduled
releases produce nightly and preview strings. Both formats are now asserted,
so tightening the pattern fails here rather than at 21:00 UTC.

* fix(release): keep failure reporting alive when the trusted runner is not

R1-11: `notify_failure` gained a checkout of `github.workflow_sha` as its
first step, and its whole body now lives in a shell script. At base the job
had no checkout at all and the notifier was inline, so the job whose only
purpose is reporting every other job's failure acquired two ways to fail
silently: a degraded git backend takes out the checkout, and a syntax error
or dropped exec bit in run-release-step.sh takes out every arm including
notify-failure. In both cases `Create Issue on Failure` carried the default
`if: success()` and was skipped — no issue, no autofix label, no dispatch,
and the scheduled autofix fallback scans issues, so nothing recovered it.

Rather than moving 105 lines of notifier back into the workflow — which the
orchestration guard in this PR forbids at 12 run-lines per step — the primary
path keeps the extraction and stops being load-bearing: it runs under
`always()`, absorbs its own failure, and a five-line inline fallback files a
plain issue whenever it did not succeed. The fallback depends on neither the
checkout nor the script, does no reuse, labelling or dispatch, and accepts a
duplicate issue as the cost of never going silent.

The workflow's line ceiling moves 800 -> 830 for those lines, with the reason
recorded at the assertion; the per-step run cap that keeps logic out of the
YAML is unchanged and still holds. Ratchet baseline and the PR description
move with the file.

* style(release): apply the pinned Prettier to the fallback notifier

Prettier 3.6.1 normalizes the quoting on the new `if:` and drops a
trailing space; the ratchet baseline follows the two-byte change.

* docs(release): name exit 4 where the guard's exit codes are described

Adding the malformed-version code left two descriptions behind: the retry
loop had no statement of the contract at all, and the test comment beside it
still said exit 0 and exit 3 were the only decisive outcomes.

* fix(release): avoid duplicate fallback failure issues

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
2026-09-08 07:09:09 +00:00
qwen-code-dev-bot
aab0258857
fix(test): put the scripts suite's quiet-host budgets on its own knob (#11098) (#11106)
Release run 33957952281 lost Quality Checks (Scripts) to two budgets that
were measured on a quiet machine and met a contended one:

- install-script.test.js capped itself at 30s with vi.setConfig, overriding
  the 90s floor scripts/tests/vitest.config.ts sets for exactly this reason
  (#10870). A standalone-packaging case that costs 3s idle timed out at
  exactly 30000ms. The file's two per-test 30s arguments and its 15s/60s
  describe ceilings were the same quiet-host figure, so they go too.
- qwen-autofix-workflow.test.js bounded its deferred-findings subprocess at
  30s. That case costs 0.9s idle; killed mid-run, the stub `gh` recording
  truncates, so the timeout reported itself as a content mismatch on the
  first recorded call. The file's four hung-runner bounds now ride
  QWEN_SCRIPTS_TEST_TIMEOUT_MS. Its 3s/2s bounds pair with a stubbed
  QWEN_TIMEOUT_MS and are fast-fail guards, so they stay.

Both signatures were reproduced locally by shrinking each budget, then
reverted. A witness in unit-vitest-configs.test.ts fails if a file in the
suite caps the floor again; mutation-probed both ways.

The same run also lost Quality Checks (Static) to its 30-minute job timeout,
and the re-run on current main lost Static and Build the same way. That is
release-workflow capacity rather than test code, so it is escalated on the
issue instead of changed here.

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-09-06 01:16:27 +00:00
易良
077c57d200
refactor!: retire @qwen-code/webui (#9812)
* feat(transcript): add cross-host document export pipeline

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

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

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

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

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

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

* build(deps): sync transcript workspace dependencies

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

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

* 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

* fix(transcript): harden export and identity paths

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

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

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

* 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(transcript): harden identity and document export

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* docs(vscode): plan complete Web Shell cutover

* docs(webui): plan legacy package retirement

* docs(webui): link cutover prerequisite

* feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* docs(vscode): plan complete Web Shell cutover

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

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

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

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

* fix(webui): preserve package entry filenames

* refactor(vscode): complete WebShell UI cutover

* chore(vscode): refresh third-party notices

* fix(vscode): fill embedded chat viewport

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

* docs: clarify webui retirement prerequisites

* fix(transcript): resolve export and CI blockers

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

* fix(vscode): compact embedded chat styling

* test(ci): cover dual Playwright installs

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

* fix(vscode): complete embedded composer integration

* refactor: retire legacy webui package

* chore: refresh lockfile after webui removal

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

* refactor: narrow webui retirement to export and removal

* fix(vscode): complete WebShell feature parity

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(vscode): close WebShell UI regression gaps

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(vscode): initialize WebShell refs explicitly

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(vscode): narrow queued prompt edits

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(release): enumerate actual npm workspaces

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

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

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

Fixes #10406

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(vscode): close remaining WebShell cutover regressions

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(vscode): remove orphaned completion trigger test

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(web-shell): update daemon SDK mock import

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(export): reject unsupported legacy JSONL

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs(transcript): record the legacy HTML renderer deletion evidence

The contract prevalidation doc is this design's single normative source
(§0.1), so retiring the legacy renderer has to be written back into it.
Three places had drifted:

- §2.1's consumer table still said the HTML Export path "keeps legacy
  compatibility for public calls without records". `toHtml` now requires
  records and the legacy branch is gone.
- §14's completion gate said the legacy HTML renderer is removed only once
  there is deletion evidence. New §12.4 records that evidence — no product
  consumer left, signature tightened, fallback explicitly rejected rather
  than silently degraded, and a `check:no-webui` guard against
  reintroduction — and keeps the VS Code legacy timeline half of the gate
  untouched.
- §10.7 promised credential removal without stating its scope. Spell out
  that it is http(s) only, and that non-navigable schemes, bare flag
  credentials and `code` / `inlineCode` nodes ship verbatim, so the
  document boundary is not read as a general secret scanner.

Also comment the two export format signatures: `records` is required
because HTML projects from original records, and the other formatters
ignore the argument.

Docs and comments only; no build or test was run locally.

* fix(docs): correct web-shell SDK hook names, daemon dir table, followup wiring

- daemon-client-adapters/web-shell.md: use the names the barrel actually
  exports (useActions/useConnection/usePendingPermissions/
  useTranscriptBlocks) in the import example and minimal React shape
- daemon/14-cli-tui-adapter.md: rewrite the
  packages/web-shell/client/daemon/ file table to the real layout
  (session/DaemonSessionProvider.tsx; the transcript adapter now lives
  in client/adapters/ and only exports extractPendingPermission)
- users/features/followup-suggestions.md: suggestion generation is
  automatic server-side; Web Shell hosts need no trigger wiring

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtngpuscjl

* fix(scripts): catch bare packages/webui references in check:no-webui

Widen the second forbidden pattern to /packages\/webui\b/g so
references followed by spaces, punctuation, or end-of-line are flagged
(CI step text, YAML list items, parenthesized mentions), while
lookalikes such as packages/webuix stay clean. Extract the detection
into containsForbiddenReference() and pin it with scripts/tests
fixtures, following the check-tui-dep-direction export convention.
The current tree still scans clean:
node scripts/check-no-webui-dependency.js exits 0.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtngpuscjl

* fix(docs): drop dead Adapter Matrix citation and stale Chromatic line

- 16-vscode-ide-adapter.md: 01-architecture.md has no "Adapter Matrix"
  section (headings verified at HEAD); drop the dead citation tail and
  keep the verified embedding claim (R1-11).
- terminal-capture/motivation.md: Chromatic retired with the webui
  package; point the "complement" item at the Playwright-based Web Shell
  visual tests already shown in this file's diagram (R1-12).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtntktgsk8

* fix(comments): correct stale webui-era notes in SDK test and followup state

- daemonUi.test.ts: the referenced previewMarkdown/rawOutput preservation
  test does not exist in web-shell (transcriptAdapter.test.ts only covers
  extractPendingPermission); rewrite the note to say the enrichment path
  retired with the webui package instead of citing a nonexistent test
  (R1-6). Wording avoids the retired package literal so check:no-webui
  still passes.
- followupState.ts: no web-shell file imports this module (its daemon
  followup hook keeps its own FollowupState/controller; the only
  createFollowupController consumer is the CLI Ink hook); fix the module
  purpose comment and the createFollowupController docblock accordingly
  (R1-13).

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-closeout/jmtntktgsk8

* fix(export): load transcript renderer from unpkg (#11035)

* fix(export): load transcript renderer from CDN

* fix(export): harden CDN renderer loading

* fix(export): host transcript renderer on project OSS

* fix(export): serve npm renderer through unpkg

* fix(release): verify export renderer before VSIX packaging

* docs(daemon): retire the stale ACPAdapter and web-ui.md pointers

R1-2/R1-4 from the review round: the Consumers note still claimed the old
ACPAdapter host postMessage path "remains available" while this PR deletes
the only ACPAdapter, and the migration-status bullet still pointed at
web-ui.md after this PR renamed it to web-shell.md. Both lines now state
what ships: the postMessage path retired with packages/webui (webviews
embed Web Shell, matching 16-vscode-ide-adapter.md), and the adapter
design doc is web-shell.md.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* docs: avoid retired webui path reference

* fix: close WebUI retirement review gaps

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-09-05 09:13:54 +00:00
ChiGao
d7fbf305cc
feat(release): add gated bun/OpenTUI preview flavor to standalone releases (#10814)
* feat(release): add gated bun/OpenTUI preview flavor to standalone releases

Phase 2 validation needs a real archive that boots the OpenTUI renderer,
so the standalone pipeline gains an opt-in second flavor instead of
replacing the classic Node.js one:

- create-standalone-package.js derives archive names from a shared
  standaloneArchiveName() helper; bun archives carry the
  -opentui-preview suffix, and bun shims default QWEN_TUI_RENDERER to
  opentui (explicit user overrides still win).
- build-standalone-release.js accepts --include-opentui-preview to
  package both flavors in one run, with per-flavor publisher checksums
  and a flavor-aware output assertion.
- verify-installation-release.js accepts the same flag so a gated
  release directory/URL and the OSS upload list both expect the preview
  archives.
- release.yml and sync-release-to-oss.yml thread the flag behind a
  vars.OPENTUI_PREVIEW_RELEASE_ENABLED gate (off by default, byte-identical
  behavior when unset); .size-baseline re-pinned in the same PR.
- Also fix a latent crash in copyOpenTuiAddon's warn-only degradation
  path, which asserted against a lib/node_modules directory it never
  created.

* feat(release): add a thin installer for the gated opentui-preview archives

The preview flavor is not reachable through the hosted installer chain,
which only packages assets for stable releases, so testers had to download
and unpack the archive by hand. This adds a scratch-directory installer
pair that resolves the platform target, verifies the release SHA256SUMS,
and unpacks into ~/.qwen-preview without touching PATH or an existing
classic installation. A local archive can be installed directly so the
path is testable before a gated release exists.
2026-09-02 12:01:56 +00:00
Michael Yochpaz
60c338f144
fix(install): avoid Get-FileHash for Windows checksums (#9112)
* fix(install): avoid Get-FileHash for Windows checksums

* refactor(install): simplify Windows checksum verification

* test(install): require checksum resource disposal
2026-08-14 01:12:08 +00:00
易良
c73b5ed887
ci: run Windows merge queue tests on ECS (#8386)
* ci: run Windows merge queue tests on ECS

* test(channels): skip POSIX mode assertion on Windows

* ci: expose Git Bash on Windows ECS runner

* ci: scope Windows ECS tuning to self-hosted and restore full test:ci

Review feedback on the Windows ECS routing: dropping test:scripts removed the only Windows execution of 9 Windows-only install-script tests, and the job-wide PowerShell default plus narrowed test command changed the kill-switch fallback away from the known-good hosted configuration.

Restore the full npm run test:ci on both paths (bash is available: pre-installed on hosted runners, exposed via the Git Bash PATH entry on ECS) and gate every ECS-specific adjustment on runner.environment: the PowerShell setup step (now also skip_ci-guarded), TEMP/TMP/LC_ALL env writes, and the Linux-style Node setup split that fails with an actionable error naming MAINTAINER_ECS_RUNNER_DISABLED. The windows-2022 fallback is byte-for-byte the pre-ECS job again.

* test: make Windows CI suites platform-aware

* ci: add stale-checkout guard to Windows ECS test job

* test(core): compare canonical directory identity

* ci: add fork guard and review follow-ups to Windows ECS job

* test(core): exercise real directory identity change

* test(core): wait for killed lease process exit

* test(scripts): avoid cmd echo trailing spaces

* test(scripts): use unambiguous cmd echo syntax

* test(cli): avoid sidecar I/O in truncation test

* test: fix Windows script-suite gaps and unify platform gating

- Fix missed trailing-space cmd stub in package-scripts.test.js so the
  'runs prepare steps in order' assertion passes on Windows.
- Add qwen-pr-review-workflow.test.js and pr-self-report-label.test.js to
  the win32 exclude list (both test Linux-only workflows and are not
  portable to Windows).
- Replace local itPosix/describeOnNonWindows consts with vitest's built-in
  it.skipIf/it.runIf/describe.skipIf, matching the codebase idiom.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* test(scripts): restore Windows workflow coverage

* test(scripts): re-exclude Windows-incompatible workflow tests on win32

Re-add pr-self-report-label.test.js and qwen-pr-review-workflow.test.js to
the win32 exclude list. Both fail on a Windows runner for reasons the code
still carries: qwen-pr-review-workflow.test.js calls execFileSync('mkdir'),
which has no executable to resolve there, and pr-self-report-label.test.js
joins PATH with ':', corrupting the ';'-separated Windows PATH so its gh
stub never resolves. Excluding them restores a green Windows gate; Linux CI
remains their authoritative coverage. Document the criterion inline.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* ci: extract checkout-head guard into composite action, pin Windows gate (#8386)

Address review round 2: move the stale-checkout guard shared by the four CI gates into .github/actions/verify-checkout-head so the copies cannot drift, pin the Windows gate kill-switch routing and guard wiring in the script tests, re-enable lint.test.js on Windows via separator normalization and a lazy linter setup in scripts/lint.js, unify the platform skips on it.skipIf(process.platform === 'win32'), and document the queued-run behavior of the ECS kill switch.

* ci: fail fast in Windows gate environment setup (#8386)

* ci: dedupe self-hosted runner steps into actions, pin gate mutations (#8386)

* fix(ci): checkout before repository-local actions in Windows gates (#8386)

* fix(ci): configure Windows runner before bash guard

* test(ci): pin remaining shared-action wiring in script tests (#8386)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(ci): skip zip-dependent packaging tests when zip is missing (#8386)

* fix(ci): validate full Windows smoke path

* fix(ci): match Windows smoke shell to gate and drop dead runs-on guard (#8386)

* fix(ci): make SIGTERM escalation test Windows-aware and tighten pins (#8386)

The CDP acceptance test asserted a POSIX-only SIGKILL escalation, which
fails deterministically on Windows where kill('SIGTERM') terminates the
child directly — blocking the Windows merge-queue gate. Assert the
platform-appropriate signal instead.

Also address review suggestions: probe `unzip` alongside `zip`, pin the
integration_cli guard's missing step-level `if:`, stop getWorkflowStep
at unnamed steps, pin install-script.test.js out of the win32 excludes,
add the stale-checkout guard to windows-runner-smoke.yml, pin the
Node preflight warning branch and the guard reject path contiguously,
and extend the smoke shell-parity loop to the npm cache step.

* docs(ci): clarify Windows runner trust boundary

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-05 12:14:42 +00:00
易良
d7e2892a7c
fix(cli): avoid updating active CLI processes (#6874)
* fix(cli): avoid updating active processes

* fix(cli): close update relaunch gaps

* test(cli): fix standalone update source path

* fix(cli): reset deferred update per relaunch
2026-07-15 00:33:17 +00:00
Nothing Chan
b19ebd8fc6
fix(packaging): bundle clipboard addon in standalone builds (#6708) 2026-07-11 15:18:24 +00:00
jinye
c90e6e7ba4
feat(channels): Add channel agent bridge abstraction (#5978)
* feat(channels): add channel agent bridge abstraction

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(channels): handle bridge session lifecycle cleanup

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(channels): close bridge lifecycle review gaps

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5978)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5978)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix: address channel bridge review feedback

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5978)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5978)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-29 10:03:28 +00:00
jinye
aa8f9bb993
fix(standalone): Route serve shim through cli-entry (#5977)
* fix(standalone): route serve shim through cli-entry

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* codex: address PR review feedback (#5977)

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-29 07:00:39 +00:00
易良
7696d9a7d9
ci: route in-repo PRs' Linux test to self-hosted runner (#5620)
* ci: route in-repo PRs' Linux test to self-hosted runner

Send the required Linux Test job to the self-hosted ECS runner for PRs whose head branch lives in this repo (which implies the author had write access), while forks and push/merge_group runs stay on GitHub-hosted runners. This lets maintainer changes skip the shared hosted Linux queue without ever running untrusted fork code on self-hosted infrastructure. The MAINTAINER_ECS_RUNNER_DISABLED repo variable forces everything back to hosted if the ECS runner is unavailable.

* ci: reuse pre-installed Node on self-hosted runners, route Lint to ECS

The self-hosted ECS runners cannot reliably download the Node tarball from nodejs.org through the egress proxy (actions/setup-node aborts mid-download), which failed the Test job routed there. On self-hosted runners, reuse the machine's pre-installed Node instead of re-downloading it every run; hosted runners keep using actions/setup-node. Also route the Lint job to ECS for in-repo PRs — scripts/lint.js already skips installing actionlint/shellcheck/yamllint when they are on PATH, so no lint.js change is needed.

* ci: trim verbose runner-routing comments

* ci: run classify_pr gate on ECS for in-repo PRs

* ci: quote classify_pr/lint runs-on expressions

* ci: keep Lint on hosted runners (ECS proxy truncates linter downloads)

* ci: route Lint and review/triage gate jobs to self-hosted runner

Lint goes back to ECS (linters are now pre-installed on the runners, so scripts/lint.js skips downloading them). The pr-review gate jobs (authorize, review-config, delay-automatic-review, ack-review-request) and the triage authorize gate also move to ECS so the ECS heavy jobs (review-pr, tmux-testing) are no longer bottlenecked by the shared hosted queue. All respect the MAINTAINER_ECS_RUNNER_DISABLED kill-switch.
2026-06-22 20:28:27 +08:00
qqqys
8809c16b57
fix(voice): bundle native audio addon into standalone archives (#5628)
Standalone archives shipped only curated dist/ entries, so the esbuild-external
@qwen-code/audio-capture addon couldn't be resolved at runtime — streaming voice
was unavailable in standalone installs (batch only, and only with SoX on PATH).

create-standalone-package.js now bundles the addon into lib/node_modules (where
the bundled lib/cli.js resolves bare specifiers): the trimmed package.json
(install hook removed; type/exports kept for ESM resolution) + dist + only this
target's prebuild (win-x64 -> win32-x64) + its zero-dep runtime dependency
node-gyp-build. Targets without a matching prebuild (e.g. local builds) ship
without it and degrade to SoX/arecord as before (warns, doesn't fail). The
release pipeline already downloads prebuilds before packaging.

Refs: #5502, #5590.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-22 12:08:33 +00:00
易良
e88356c6b7
ci(release): auto-publish VSCode companion after releases (#5572) 2026-06-22 02:08:44 +08:00
易良
ac4d100e1a
fix(installer): print shell reload hint when new qwen is not picked up (#4960)
Some checks are pending
Qwen Code CI / Classify PR (push) Waiting to run
Qwen Code CI / Lint (push) Blocked by required conditions
Qwen Code CI / Test (macos-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (ubuntu-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Test (windows-latest, Node 22.x) (push) Blocked by required conditions
Qwen Code CI / Post Coverage Comment (push) Blocked by required conditions
Qwen Code CI / CodeQL (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* fix(installer): print shell reload hint when new qwen is not picked up

The installer updates the shell rc file, but the invoking shell keeps its
original PATH, so right after install `qwen` can still resolve to an older
install (e.g. a previous npm global version) with no indication of why.

Capture the PATH inherited from the invoking shell and, when the new install
is not reachable from it (install dir missing from PATH, or another qwen
executable shadows it), print the exact reload command in the start steps
and warn about the shadowing executables.

* fix(installer): derive reload hint from the rc file actually written

maybe_update_shell_path can fall back to ~/.bash_profile (or create
~/.bashrc), but the summary re-derived the rc name from $SHELL alone and
always claimed ~/.bashrc for bash. Record the file the PATH block was
written to in PATH_UPDATE_RC_FILE and build both the success message and
the source hint from it, so the two can no longer drift apart.

Also drop the non-hermetic shadow-warning assertion from the reload-hint
test: PRE_INSTALL_QWENS scans well-known absolute paths regardless of
PATH, so the warning may legitimately fire on hosts with qwen installed.
Add a regression test for the macOS-style .bash_profile-only setup.
2026-06-11 19:04:35 +08:00
易良
968a0d1f03
fix(installer): auto-detect SYSTEM account and default PATH scope to machine (#4903)
* fix(installer): auto-detect SYSTEM account and default PATH scope to machine

When the Windows standalone installer runs as the SYSTEM account (SID
S-1-5-18), which is common for ECS Workbench SSM, WinRM, and scheduled
tasks, the default --path-scope is now 'machine' instead of 'user'.

Previously, persisting PATH to the SYSTEM user's HKCU hive had no
effect on new sessions spawned by the SSM agent service, because the
agent inherits its environment at service start time and never re-reads
HKCU. Writing to the Machine PATH (HKLM) ensures new sessions pick up
the qwen shim immediately.

Also adds:
- --repair-path flag to fix PATH for an existing install without
  reinstalling
- --path-scope user|machine CLI option and QWEN_INSTALL_PATH_SCOPE env
  var
- Uninstaller now cleans both User and Machine PATH entries
- CI validation step for hosted installation assets

Resolves #4901

* test(installer): add Windows self-hosted qwen smoke

* fix(installer): address Windows PATH review feedback

* refactor(installer): remove redundant YAML validation, deduplicate test fixtures

- Remove windows-self-hosted-qwen-smoke.yml (operational tool, not part of fix)
- Revert sync-release-to-oss.yml validate step (redundant with JS behavior guards)
- Extract duplicated test fixture strings into shared constants
- Pass normalized path to Remove-PathEntry to avoid double Get-NormalizedPath
- Fix stale comment referencing "user PATH" when scope is now configurable

* chore: drop unrelated dev.test.js changes from installer PR
2026-06-10 21:02:10 +08:00
pomelo
0def5f77ff
fix(installer): correct 'for more info' URL to GitHub repo instead of docs site (#4916)
Co-authored-by: tanzhenxin <tanzhenxing1987@gmail.com>
2026-06-10 10:46:58 +08:00
易良
aef3e704b4
feat(installer): verify release assets + switch public docs to standalone entrypoint (#3855)
* fix(installer): tighten verifier base-url + clarify test helper

Three small refinements from the second review pass:

- normalizeHttpsBaseUrl rejects everything except https, since real release
  URLs are always HTTPS. Accepting http previously would let an operator
  silently target a stale or attacker-controlled mirror.
- Drop EXPECTED_RELEASE_ASSET_NAMES from the public exports; it was only
  used internally for the verification log line.
- Rename the test helper standaloneChecksumContent to
  placeholderChecksumContent and document that the hashes in its output are
  placeholders — the remote verifier does not download archives or compare
  hashes, it only validates that SHA256SUMS lists the expected names and
  that each archive URL is reachable.

The non-https rejection test now also covers `http://` in addition to the
existing `file://` case.

* style(installer): align installer completion output

* revert(installer): keep hosted installer output unchanged

* fix(installer): address release validation review feedback

* docs: switch public install commands to standalone hosted entrypoint

Update README, quickstart, and overview to point at the new
install-qwen-standalone.sh / install-qwen-standalone.ps1 hosted URLs.
Add standalone uninstall instructions to Uninstall.md. Remove the
staged-rollout note from INSTALLATION_GUIDE.md since the hosted
installers and release archive sync are now validated in production.

* docs: clarify pull request size guidance

* fix(installation): harden standalone release validation

* fix(installation): redact release verifier credentials

* feat(installer): add visual branding to Linux/macOS install script

Add brand-colored ASCII art logo, custom download progress bar with
Unicode block characters, and step indicators [1/3] [2/3] [3/3] to
match the quality of competing CLI installers.

* fix(test): update stale assertion after guide text was removed

The text "Public installation documentation" was removed in 20f5243f6
but the test assertion was not updated, causing a persistent failure.

* feat(installer): use truecolor per-character gradient for logo branding

Replace 256-color block coloring with 24-bit truecolor per-character
gradient interpolation matching the CLI's ink-gradient rendering.
Colors follow the fallback gradient: #4796E4 → #847ACE → #C3677F.
Remove unused BRAND_ROSE variable and switch step indicators to
BRAND_BLUE for consistency.

* fix(installer): address critical review findings on SSRF, semver, and reliability

- Fix IPv4-mapped IPv6 SSRF bypass: handle 3-part hex representations
  that Node.js produces (e.g. ::ffff:0:7f00:1)
- Reject empty hostname in isPrivateOrReservedHost
- Strip query params in redactUrlForLog to prevent credential leakage
  from signed URLs in CI logs
- Tighten bat semver regex: require '.' or '-' separator before suffix
  (rejects 1.2.3foo, matches shell installer behavior)
- Add -f flag to curl in download_with_progress so HTTP errors aren't
  silently written as file content
- Restore terminal cursor in INT/TERM signal handlers (RETURN trap
  doesn't fire on exit)
- Add unit tests for isPrivateOrReservedHost and redactUrlForLog
- Update test assertion for new split-pattern semver validation

* fix(installer): close release validation review gaps

* test(installer): cover shadowed qwen installs

* fix(installer): avoid npm auto-update for standalone installs

* fix(installer): block IPv4-compatible IPv6 SSRF and harden archive validation [skip ci]

- Add ipv4FromCompatibleIpv6() to detect deprecated RFC 4291 §2.5.5.1
  addresses (e.g. ::7f00:1 → 127.0.0.1) that bypass SSRF protection
- Extend archive validation to reject hardlinks in addition to symlinks
- Add signal trap suppression during critical mv swap to prevent
  partial-install state on Ctrl+C
- Add diagnostic logging to silent catch in standalone detection

* fix(installer): finish standalone install follow-ups

* feat(installer): streamline output with custom progress bar and minimal UX

Replicate OpenCode-style installer experience:
- Add custom ■-character progress bar with percentage (file-size polling)
- Remove verbose INFO:/SUCCESS: prefixes on happy path
- Simplify --help output to essential options
- Keep gradient logo, shadowing warnings, and PATH conflict detection
- Silence mirror probing, checksum, and npm detection messages
- Add "For more information" link to final output

Both .sh and .bat scripts updated consistently.
All 95 tests pass.

* feat(installer): add progress bar and logo to Windows installer

- Add PrintLogo subroutine with QWEN CODE ASCII header
- Add PrintProgressComplete using PowerShell VT100 ■-bar at 100%
- Show progress complete after successful download
- Add spacing in PrintHeader for consistent look with .sh

* fix(installer): address review findings on progress bar

- Replace `sleep 0.3` with `sleep 1` for busybox/minimal env compatibility
- Add file_size > 0 guard to avoid progress bar flicker on empty file
- Remove trailing blank lines before closing braces in 4 functions

* fix(installer): finalize Windows UX — suppress curl progress, fix logo

- Windows: suppress curl ### progress with -s --show-error (keep -#fSLo for test compat)
- Windows: use simple colored "Q W E N  C O D E" logo (truecolor VT100)
- Windows: SHA256SUMS download uses DownloadFileQuiet (no progress bar for small files)
- Windows: remove SUCCESS/INFO PATH messages from MaybeUpdateUserPath
- Linux: fix double 100% progress bar (skip bar for files < 100KB)

* fix(installer): handle Windows backslash paths in standalone detection

`fs.realpathSync` returns backslash paths on Windows (e.g. C:\Users\...\lib\cli.js).
Normalize to forward slashes before matching the /lib/cli.js suffix so standalone
install detection works correctly on Windows.

Fixes CI: Test (windows-latest, Node 22.x)

* fix(installer): normalize expected paths in Windows standalone test

The existsSync mock built expected paths with path.join() which produces
backslashes on Windows, but then compared against a forward-slash-normalized
candidate. Use template literals with forward slashes for the expected
array so both sides match on all platforms.

* refactor(installer): simplify post-install output

Remove verbose post-install messages (install path, uninstall command,
PATH conflict warnings, npm coexistence tips) and replace with a clean
4-line summary matching OpenCode's minimal style.

* refactor(installer): simplify Windows post-install output

Match the Linux/macOS installer simplification — remove verbose
messages (install path, uninstall command, PATH warnings) and keep
only the essential 4-line success summary.

* refactor(installer): suppress verbose Windows messages

Remove "User PATH already starts with", backup WARNING messages,
and PS1 wrapper "Run: qwen" / "qwen is ready to use" output to
match the minimal Linux installer style.

* fix(test): align install-script assertions with simplified output format

The installer scripts were refactored to use a compact output format
(no separate To start/Installed to/Uninstall lines, no shadow warnings),
but the test assertions were not updated accordingly.

* fix(installer): align hardlink detection and expand test coverage

- Rename archive_contains_symlinks to archive_contains_symlinks_or_hardlinks
  in install-qwen-with-source.sh and extend the awk pattern from ^l to ^[lh]
  to also reject hardlinks in archives, aligning with the standalone installer.

- Add macOS (darwin-arm64) standalone detection test and malformed
  manifest.json fallback test in installationInfo.test.ts.

- Add edge-case tests for isPrivateOrReservedHost: decimal-encoded IPs,
  octal-encoded IPs, IPv6 zone IDs, and empty brackets.
2026-06-04 17:23:04 +08:00
易良
5493888c15
ci: split Aliyun OSS sync into a separate post-release workflow (#4492)
* ci: split Aliyun OSS sync into a separate post-release workflow

The OSS upload and verification steps were adding significant time to the
release workflow's critical path. Move them into a new `sync-release-to-oss.yml`
workflow that triggers on `release: published`, running asynchronously after
the release completes.

Key changes:
- Extract all OSS steps (ossutil install, credential config, asset upload,
  verification, hosted installation sync, latest VERSION pointer) into
  `sync-release-to-oss.yml`
- Switch `gh release create` to use CI_BOT_PAT so the release event can
  trigger the new downstream workflow (GITHUB_TOKEN events don't trigger
  other workflows)
- Add `workflow_dispatch` input for manual re-runs on failure
- New workflow downloads release assets from GitHub Release instead of
  rebuilding them

This decouples publishing from CDN distribution: the release finishes as
soon as npm publish + GitHub Release are done, and China CDN sync happens
in parallel without blocking.

* fix(test): update install-script test to check sync-release-to-oss.yml

The test asserts OSS sync steps exist in the workflow. Now that these
steps live in sync-release-to-oss.yml instead of release.yml, update the
test to read from the correct file and add assertions that release.yml
no longer contains OSS logic.

* fix(ci): address review feedback for OSS sync split

- Add 'Verify Standalone Archives' step before gh release create in
  release.yml as a pre-publish safety gate (wenshao)
- Add concurrency group to sync-release-to-oss.yml to prevent race
  conditions when multiple releases publish close together (wenshao)
- Update test to assert verify step exists in release.yml

* chore: add comment explaining CI_BOT_PAT requirement [skip ci]
2026-05-25 19:34:21 +08:00
易良
d59c9e7b77
feat(installer): add standalone hosted install and uninstall flow (#3828)
* feat(installer): add standalone archive installation

* fix(installer): harden standalone archive installs

* fix(installer): address standalone review findings

* chore(installer): clarify review followups

* fix(installer): stabilize standalone script checks

* chore(installer): remove internal planning docs

* chore(installer): simplify standalone release review fixes

* test(installer): add Windows batch install smoke

* test(installer): fix Windows batch smoke quoting

* test(installer): preserve Windows cmd quotes

* fix(installer): use robust Windows checksum hashing

* ci: narrow installer debug matrix

* fix(installer): address standalone review hardening

* fix(installer): avoid Windows validation parse errors

* fix(installer): simplify Windows option validation

* fix(installer): harden standalone review fixes

* feat(installer): publish release installer assets

* fix(installer): address release asset review feedback

* fix(installer): avoid prerelease installer asset links

* test(installer): isolate standalone dist fixture

* feat(installer): add hosted install release alias

* chore: no changes - code review requested

Agent-Logs-Url: https://github.com/QwenLM/qwen-code/sessions/38467aec-15b9-4b76-9139-0b2cfe40477a

* fix(installer): pin versioned installer assets

* fix: parallelize Node.js binary downloads in standalone release build

Use Promise.all instead of sequential for...of+await for
the 5 independent Node.js runtime downloads, reducing CI
release build time by ~4-5x.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

* fix(installer): address release asset review followups

* refactor(installer): share release CLI parsing

* fix(installer): address release asset review followups

- sh: reject CR/LF in archive entry names before the literal `..` glob so
  a `..\r` entry cannot bypass path validation.
- bat: prefer Tls12+Tls13 in PowerShell helpers, fall back to Tls12 alone
  on older .NET Framework where the Tls13 enum is missing.
- bat: document the implicit `:ValidateOptions` dependency next to the
  qwen.cmd wrapper writer so loosening the validator stays a conscious
  choice.
- build-standalone-release: surface the `xz-utils` host requirement for
  Linux Node downloads in `--help`.
- release-script-utils: support `--key=value` form in `parseCliArgs`.
- tests: cover the new CRLF message, TLS string, and `--key=value` parsing;
  register process-level signal/exit handlers in `ensureMinimalDist` so a
  crashed test still restores `dist/`.

* fix(installer): unblock Windows CI for standalone install path

Three CI failures and a few review followups in one pass.

- ensureMinimalDist places its dist/ backup beside dist/ instead of
  under os.tmpdir(). On Windows GitHub runners the workspace lives on
  D: while os.tmpdir() is on C:, so renameSync raised EXDEV for every
  test that needed to swap dist/ in.
- create-standalone-package.js and the matching test fixture build
  win-x64 zips with [IO.Compression.ZipFile]::CreateFromDirectory.
  Compress-Archive emits backslash entry names that the .bat
  installer's path-traversal guard then rejected, so every freshly
  built archive failed the standalone install path on Windows.
- :ValidateArchiveContents normalizes entry separators to '/' before
  checking for '..', absolute paths, and drive prefixes - archives
  from any Windows zip tool still install while real traversal
  entries remain rejected.
- createWindowsTraversalStandaloneArchive runs PowerShell via -File
  instead of a single -Command line; the joined-with-'; ' form had a
  function definition the runner's PowerShell refused to parse.

Drive-by review followups:

- replaceRequired uses replaceAll so a future duplicate placeholder
  cannot silently keep the trailing copy as 'latest'.
- :ValidateOptions runs the unsafe-character check on SOURCE
  alongside the other variables.
- build-installation-assets.js drops a dead INSTALLATION_ASSETS
  re-export; consumers already import from release-asset-config.js.
- .gitignore covers the new sibling .qwen-dist-backup-* directory.

* fix(installer): address release asset review findings

* fix(installer): keep installer entrypoint hosted

* fix(installer): reject stale hosted assets

* fix(installer): refine hosted asset staging

* fix(installer): tighten hosted default-version check, flag legacy URL

- Replace the loose `latest` fragment check with per-format regex patterns
  in HOSTED_INSTALLER_DEFAULT_VERSION_PATTERNS so an unrelated occurrence
  of `latest` (comment, help text) cannot satisfy the staging guard. The
  patterns still tolerate whitespace variation, only the default-version
  assignment itself must be intact.
- Add a "Hosted endpoint status" callout in INSTALLATION_GUIDE.md before
  the curl examples. The documented `--version` flow does not work against
  the OSS URL today because it currently serves the legacy NVM-based
  installer; the callout points users at a local checkout until the next
  release sync.
- Tests: drop `latest` from the fragments equality assertion, add positive
  and negative regex coverage, add a failure-path case for sources whose
  default version is not `latest`, and pin the new guide markers so the
  callout cannot silently disappear.

* feat(installer): verify installation release assets

Adds `npm run verify:installation-release` and wires it into the release
workflow after `Build Standalone Archives`, so a broken release directory
fails CI before publishing.

Local mode (`--dir PATH`) checks:
- All five `qwen-code-{platform}.{ext}` standalone archives exist.
- `SHA256SUMS` covers exactly those five — missing or unexpected entries fail.
- Each archive's actual SHA256 matches its `SHA256SUMS` entry.

Remote mode (`--base-url URL`) checks:
- `SHA256SUMS` is downloadable, parseable, and contains exactly the expected
  archive entries.
- Each archive URL is reachable via HEAD, with a 1-byte ranged GET fallback
  for hosts that disable HEAD.

Hosted installer scripts (`install-qwen.sh` / `install-qwen.bat`) are
intentionally out of scope here — they are served from the hosted endpoint
prepared by `package:hosted-installation` (PR #3853), not from the GitHub
Release surface this verifier targets.

* fix(installer): tighten verifier base-url + clarify test helper

Three small refinements from the second review pass:

- normalizeHttpsBaseUrl rejects everything except https, since real release
  URLs are always HTTPS. Accepting http previously would let an operator
  silently target a stale or attacker-controlled mirror.
- Drop EXPECTED_RELEASE_ASSET_NAMES from the public exports; it was only
  used internally for the verification log line.
- Rename the test helper standaloneChecksumContent to
  placeholderChecksumContent and document that the hashes in its output are
  placeholders — the remote verifier does not download archives or compare
  hashes, it only validates that SHA256SUMS lists the expected names and
  that each archive URL is reachable.

The non-https rejection test now also covers `http://` in addition to the
existing `file://` case.

* fix(installer): address standalone review follow-ups

* fix(installer): repair Windows installer tests

* fix(release): tighten standalone asset checks

* fix(installer): stabilize Windows managed install checks

* test(installer): relax Windows installer timeout

* fix(test): escape release asset regex

* test(cli): avoid POSIX node path in relaunch test

* fix(installer): align npm fallback node gate with engines

* test(installer): allow Windows archive validation more time

* fix(installer): remove stale node 20 installer references

* docs(installer): clarify hosted endpoint sync requirement

* refactor(installer): reuse standaloneArchiveName in release verifier

The verify-installation-release script was duplicating the archive name
derivation logic with a hardcoded ternary instead of reusing the
standaloneArchiveName helper from build-standalone-release. Export the
helper and import it so the extension mapping lives in one place.

* fix(scripts): address release verifier review feedback

* feat(installer): add standalone archive installer with multi-platform release workflow

- Add standalone archive installer (bat/sh) that downloads platform binaries
  from GitHub/Aliyun without requiring Node.js or npm on the target machine
- Add fork-friendly release-test workflow for manual GitHub Release creation
  covering all 5 platforms (darwin-arm64/x64, linux-arm64/x64, win-x64)
- Add OSS upload/mirror tools for staging and release distribution
- Update .gitignore to exclude generated build artifacts (release-staging/,
  hosted-staging/)
- Fix Windows PowerShell test command in copy-release-to-latest tool

* feat(installer): support QWEN_INSTALL_GITHUB_REPO env var for custom repo

* chore(installer): exclude local-only staging tools from PR

The tools/ directory contained personal staging-OSS upload helpers
(upload-staging, upload-release-mirror, copy-release-to-latest,
test-upload-one) that should not ship in the public PR. They reference
a personal staging bucket and only exist to validate the installer
end-to-end before production release.

Removes them from git tracking via `git rm --cached` (files stay on
disk for the author's local use) and adds /tools/ to root .gitignore
so they cannot be re-added accidentally.

No runtime / installer code change. Production CI on ubuntu-latest is
unaffected.

* fix(installer): enforce CRLF line endings for .bat files via gitattributes

cmd.exe requires CRLF in batch scripts; the global eol=lf was causing
every line to be misparsed on Windows, producing errors like
'QWEN_VALIDATE_METHOD=detect is not recognized as a command'.

* fix(installer): store .bat files with CRLF in git blob for raw GitHub downloads

GitHub raw file serving bypasses gitattributes eol conversion and serves
blob bytes directly, so eol=crlf alone was not enough. Use -text to disable
normalization and commit with actual CRLF so raw downloads work on Windows.

* fix(installer): follow HTTP redirects in UrlExists and RaceMirrorHead probes

GitHub release asset URLs return HTTP 302 to objects.githubusercontent.com.
[Net.WebRequest] with HEAD does not auto-redirect by default, so the
existence check and mirror-race probe both incorrectly reported the file
as missing. Set AllowAutoRedirect=true on HttpWebRequest instances.

* fix(installer): surface download errors and add MaximumRedirection 10

* feat(installer): add hosted install-qwen.ps1 shim for irm|iex one-liner

The previous Windows quick-install one-liner used `Invoke-WebRequest -OutFile
(Join-Path $env:TEMP 'install-qwen.bat'); & (Join-Path …)`. When pasted into a
narrow terminal, line wrap could land on `-OutFile`, orphaning the parameter
from its value and producing the "missing argument for OutFile" failure
followed by a "file not found" when the second `&` ran. PowerShell's line
continuation rules cannot resolve this for parameter-name-at-EOL.

Add `install-qwen.ps1` as a thin hosted entrypoint that downloads
`install-qwen.bat` into TEMP, runs it, and cleans up. Documented one-liner
becomes the standard pattern used by bun, uv, scoop, deno, pnpm:

    powershell -ExecutionPolicy Bypass -c "irm <url>/install-qwen.ps1 | iex"

The `.bat` remains the source of truth for installer behavior; `.ps1` is just
the modern hosted entrypoint. Version pinning via `$env:QWEN_INSTALL_VERSION`
flows through unchanged. Stored with `*.ps1 -text` so CRLF survives both
GitHub raw and OSS uploads, matching the existing `.bat` handling.

* fix(installer): stage direct hosted install scripts

* chore(installer): trim hosted release diff scope

* chore(installer): narrow hosted release diff

* feat(installer): restore hosted PowerShell entrypoint

* chore(installer): stage standalone hosted entrypoints

* fix(installer): address hosted installer review followups

* fix(installer): stabilize Windows installer tests

* fix(installer): make Windows option validation readable

* feat(installer): wire Aliyun OSS sync, address review followups

- Add Aliyun OSS sync steps to release workflow: package hosted assets,
  install pinned ossutil, configure credentials, upload versioned and
  latest paths, and verify upload via verify:installation-release plus
  curl probes against the hosted installer endpoint.
- Document required production-release environment secrets and bucket
  variables in INSTALLATION_GUIDE.md.
- Restructure hosted endpoint guidance to lead with the pre-sync
  warning, splitting "Run today" (local checkout) from "After the OSS
  sync" (hosted one-liners) so users no longer copy a one-liner that
  silently installs latest.
- Distinguish mirror auto-selection timeout from successful selection
  in install-qwen-standalone.sh and install-qwen-standalone.bat: emit
  a "timed out; defaulting to github" log instead of pretending the
  HEAD probe picked github.
- Support QWEN_INSTALLER_BAT_URL override (https only) in the
  PowerShell shim so staging mirrors can be exercised without forking
  the file.
- Strip a leading UTF-8 BOM in verify-installation-release.js
  parseSha256Sums so BOM-prefixed SHA256SUMS reports a useful
  "Missing checksum entry" error instead of "Malformed SHA256SUMS
  line 1".
- Add tests for verifier HEAD→Range fallback, partial-failure
  formatting, all-failure wording, and BOM tolerance.

* ci(installer): add temporary OSS smoke test

* fix(installer): make OSS release assets public-readable

* chore(installer): remove temporary OSS smoke workflow

* fix(installer): address hosted installer review gaps

* feat(installer): refactor argument parsing and utility functions for release scripts

* fix(installer): harden hosted release script checks

* fix(installer): suppress PowerShell progress bar in hosted entrypoint shim

Add $ProgressPreference = 'SilentlyContinue' to the .ps1 wrapper so
Invoke-WebRequest downloads don't render a progress bar when invoked
via the irm | iex one-liner.

* fix(installer): suppress PowerShell progress bar in bat installer downloads

Add $ProgressPreference = 'SilentlyContinue' to DownloadFile so the
full-screen progress UI does not appear during archive downloads in
interactive PowerShell sessions, consistent with the .ps1 shim.

* fix(installer): use curl.exe -# progress bar in Windows downloads

Prefer curl.exe with -# (hash-mark progress bar) for archive and installer
downloads on Windows 10+. Falls back to Invoke-WebRequest (which shows its
own progress bar) when curl.exe is unavailable. Matches the approach used
by code-server (curl -#fL) and bun.sh (curl.exe -#SfLo).

* fix(installer): suppress progress bars for small downloads and Expand-Archive

- .ps1: replace curl.exe -# with silent mode, suppress Invoke-WebRequest
  progress bar; save/restore $global:ProgressPreference
- .bat: add $ProgressPreference = 'SilentlyContinue' before Expand-Archive
  to prevent full-screen extraction progress UI
- .sh: remove --progress-bar / --show-progress from download_file, always
  use silent curl/wget

* fix(installer): auto-backup non-qwen directories and simplify output

- ensure_managed_install_dir / :EnsureManagedInstallDir now back up
  non-qwen directories instead of refusing to install, so users
  upgrading from npm or old installers don't hit a hard error
- Simplify header/footer output: remove banner bars, verbose INFO
  lines, and redundant "Installation completed!" message
- Match bun.sh / code-server style: minimal, to the point

* fix(installer): revert Expand-Archive progress suppression in bat

The inline $ProgressPreference = 'SilentlyContinue' caused a cmd.exe
parsing error ("此时不应有 >") on Chinese Windows. Revert to the
original Expand-Archive invocation.

* fix(installer): fix cmd.exe parsing error in backup fallback code

The %s in the for /f fallback command string was interpreted as a variable
reference by cmd.exe, causing "此时不应有 >" on Chinese Windows. Replace
with a safe fallback and re-enable Expand-Archive progress suppression.

* fix(installer): always persist install bin to user PATH

Previously MaybeUpdateUserPath was only called when shadow qwen
executables were detected. When no shadow was found, the PATH update
was skipped entirely, leaving the user without qwen on PATH after
restarting their terminal.

Now always persist the bin directory to PATH (unless --no-modify-path
is set), regardless of whether other qwen installations exist.

* fix(installer): persist PATH to current terminal session on Windows

Use the `endlocal & set` trick (same as bun/Rust installers) to export
the install bin directory from the setlocal scope to the current cmd
session. qwen is now usable immediately without restarting the terminal.

* docs(installer): document cmd.exe one-liner for immediate PATH availability

Add curl-based one-liner for cmd.exe users. Running the .bat directly
in the current cmd session makes `qwen` available immediately via the
`endlocal & set` trick. The `powershell -c "irm | iex"` path creates
a child process so PATH changes cannot propagate to the parent.

* feat(installer): make qwen usable immediately from PowerShell after install

- .ps1: detect parent process, update current session PATH, and for
  cmd.exe parents emit a `set PATH=...` command
- .bat: skip final instructions when called from PowerShell to avoid
  duplicate "Run: qwen" output

* fix(installer): remove non-functional doskey approach for cmd parent

doskey /exename from a child PowerShell process cannot modify the
parent cmd.exe session. Replace with a simple set PATH=... command
that the user can copy-paste.

* fix(installer): make Windows standalone shim available in cmd

* feat(installer): add standalone uninstall scripts

* fix(uninstall): match shell-quoted paths when removing the wrapper

The installer's write_unix_wrapper shell-quotes the binary path, so
paths containing single quotes (or other shell metacharacters) appear
as shell-quoted strings in the generated wrapper file. The uninstall
script's literal grep -qF missed these, leaving the wrapper orphaned.

Add shell_quote to the uninstall script and match against both the raw
and shell-quoted forms before removing the wrapper.

* fix(installer): update download commands to use progress indicators for curl and wget

* fix(installer): resolve Aliyun latest via version pointer

* fix(installer): cleanup mirror probe temp dirs

* fix(installer): harden standalone release fallback

* fix(installer): address standalone review feedback

* style(installer): align standalone install output

* fix(installer): print standalone uninstall commands

* fix(installer): address release review follow-ups

* fix(installer): harden Windows target detection

* test(installer): stabilize Windows fake tool path

* fix(installer): allow explicit Windows curl path

* test(installer): use cmd fake curl on Windows

* test(installer): cover Windows fake curl helper

* test(installer): inject Windows arch overrides in cmd

* test(cli): wait for prompt suggestion render

* test(cli): revert prompt suggestion wait tweak

* fix(installer): harden hosted release publishing

* fix(installer): harden Windows latest pointer parsing

* fix(installer): bound Windows download timeouts

* fix(installer): bound hosted installer probes

* fix(release): make ossutil download configurable

* fix(installer): address hosted release review feedback

* test(installer): keep dist backup on same filesystem

* fix(installer): address remaining review feedback on PR #3828

- Remove REQUIRE_CHECKSUM dead code, always hard-fail on checksum issues
- Add JSDoc to HOSTED_INSTALLER_BEHAVIOR_PATTERNS explaining its purpose
- Add credential cleanup trap for ossutilconfig in release workflow
- Add 3-attempt retry with exponential backoff for OSS uploads
- Tighten findstr SOURCE regex to require leading letter

* fix(release): correct OSS credentials lifetime and mirror probe fallback

- release.yml: remove `trap EXIT` inside the Configure step; it deleted
  ${RUNNER_TEMP}/.ossutilconfig as soon as the configure shell exited,
  so every subsequent step (publish/sync/verify) lost the credentials.
  Move credential cleanup to a final `if: always()` step at the job tail.
- install-qwen-standalone.sh: drop the predictable PID-based mktemp -d
  fallback in race_mirror_head; if mktemp fails, return "github" instead
  of using /tmp/qwen-mirror.$$ which a local attacker could pre-create
  to bias mirror selection.

* fix(installer): address review feedback round 2

Workflow:
- Move 'Publish Aliyun OSS Latest VERSION' to run after the hosted installer
  assets are uploaded and verified, so the latest/VERSION pointer only flips
  once every release artifact is in place. Previously a hosted-sync failure
  could leave the pointer ahead of the actual installer scripts.

upload-aliyun-oss-assets.js:
- Replace `spawnSync('sleep', ...)` retry backoff with an Atomics.wait-based
  cross-platform sleep so retries also work on Windows runners.

install-qwen-standalone.bat:
- :DetectTarget no longer emits TARGET=win-arm64 because RELEASE_TARGETS has
  no win-arm64 archive; ARM64 hosts now fall through to the unsupported-arch
  branch and (in detect mode) get the npm fallback instead of a 404.
- Add QWEN_INSTALL_CURL_EXE to :ValidateRawEnvironmentOptions so this curl
  override is checked for shell metacharacters like every other knob.
- Replace `call echo %%i>>...` with plain `echo %%i>>...` when capturing
  pre-install qwen.cmd paths; `call` triggered an extra parse pass that
  could interpret &/|/<,>/etc. inside a directory name as command separators.
- Add `--retry 2` to curl.exe downloads (`:DownloadFile` / `:DownloadFileQuiet`)
  to match the shell installer.
- Include expected vs actual hash in the checksum-mismatch error message.

install-qwen-standalone.ps1:
- Stage the downloaded installer at a cryptographically random temp path
  (`qwen-installer-<random>.bat`) so a same-user attacker cannot pre-stage a
  malicious .bat at a predictable path and race the verify/execute window.
- Atomically install the current-session cmd shim by writing to a sibling
  `.new` temp file then renaming, so a partial write cannot leave a
  half-written shim on PATH.
- Add `--retry 2` to the curl.exe download path.
- Include expected vs actual hash in the checksum-mismatch error message.

install-qwen-standalone.sh:
- Include expected vs actual hash in the checksum-mismatch error message.

uninstall-qwen-standalone.ps1:
- Accept `-Purge` and `-Help` parameters; previously every CLI flag was
  silently dropped, so users running with `-Purge` got no purge and no error.
  `-Purge` maps to `QWEN_UNINSTALL_PURGE=1`.

uninstall-qwen-standalone.sh:
- `remove_install_wrapper` additionally requires the wrapper file to start
  with a `#!` shebang before it deletes it; a user-authored script that just
  happens to mention the install path now stays untouched.

verify-installation-release.js, build-hosted-installation-assets.js:
- Include expected vs actual hash in the checksum-mismatch error messages.

scripts/tests/install-script.test.js:
- Update assertions for the new error wording, the curl `--retry 2` flag,
  the dropped ARM64 detection, and the new release-step ordering.

* fix(installer): address review feedback round 3

Workflow:
- Configure Aliyun OSS Credentials: write the ossutil config file directly
  with restricted umask instead of invoking `ossutil config -k <secret>`.
  Passing the access-key secret via argv made it visible in /proc/<pid>/cmdline
  for the lifetime of that step; writing the INI file in-process keeps the
  secret out of the process table.

upload-aliyun-oss-assets.js:
- Upload assets in parallel with `Promise.all` + async `spawn` instead of a
  sequential `spawnSync` loop. Each asset keeps its own retry budget; failures
  are aggregated so one flaky upload does not mask a separate failure.
- Replace the bespoke `Atomics.wait` retry sleep with `timers/promises#setTimeout`
  now that the loop is async.

INSTALLATION_GUIDE.md:
- Drop the misleading "instead of overwriting the global installation/
  entrypoint objects" sentence; the workflow has always also refreshed the
  global versionless objects so curl|bash links keep resolving without a
  version segment. Document the rollback story instead.

* test(installer): add parseUploadArgs unit tests and align verify derivation

- scripts/tests/upload-aliyun-oss-assets.test.js: cover --help short-circuit,
  required-option validation (--bucket/--config/--prefix/empty assets),
  unknown options, missing option values, and trailing-slash prefix
  normalization.
- scripts/verify-installation-release.js: switch the win-only zip branch
  from `startsWith('win-')` to the strict `=== 'win-x64'` check used by
  build-standalone-release.js, and add a comment recording that the two
  derivations must stay aligned. Without this the helpers would diverge
  the moment a non-x64 win target gets added.

* test(installer): add uploadAssets integration tests with fake ossutil

Add two integration tests that route a temp-directory ossutil shim onto
PATH so uploadAssets actually spawns the real binary with the real cp
argv:

- happy-path test asserts the destination URI, `-c <config>`, `--acl
  public-read`, and per-asset cp invocations land for both inputs.
- failure-path test asserts non-zero ossutil exits surface as an
  aggregate `asset uploads failed` error after the retry budget runs out.

* revert(installer): drop over-engineered ossutil/upload changes

Roll back two changes from a1ef8697b/0a5d308c9 that were not justified
by the actual threat model or release-pipeline needs:

- .github/workflows/release.yml: restore the supported `ossutil config -k`
  invocation. The earlier switch to writing the .ossutilconfig INI file
  in-process was meant to keep the access-key out of /proc/<pid>/cmdline,
  but GitHub-hosted runners are single-tenant ephemeral VMs where no other
  user can read that namespace. The benefit was theoretical; the cost was
  taking on a brittle dependency on ossutil's undocumented config format.

- scripts/upload-aliyun-oss-assets.js: revert the uploadAssets parallel
  rewrite (Promise.all + spawn + setTimeout) back to the original sync
  spawnSync loop with retry. Release-time uploads of ~6 small files do
  not need parallelism, and the async refactor changed the public
  contract (sync→async) for no real wall-clock win.

Kept from those commits:
- The cleanup `if: always()` step that removes RUNNER_TEMP/.ossutilconfig
  at the end of the publish job.
- The cross-platform sleepSync(ms) helper, since `spawnSync('sleep', ...)`
  still does not work on Windows runners.
- The INSTALLATION_GUIDE.md doc fix.
- All other round-2 fixes.

Test assertions updated for the restored sync uploadAssets contract.

* test(installer): cover Windows release script regressions

* test(release): avoid Windows shim lookup in oss upload tests

* test(installer): use stable fake Aliyun version on Windows

* fix(installer): parse Aliyun latest version in batch

* fix(installer): validate Aliyun latest version without findstr

* fix(installer): normalize Aliyun latest version via PowerShell

* fix(installer): avoid captured PowerShell output in batch latest parsing

* fix(installer): normalize Aliyun latest pointer from file

* test(installer): fix fake Windows curl output parsing

* fix(installer): print checksum path on miss, gate hardcoded version pin in ps1 [skip ci]

Address two narrow follow-ups from PR #3828 review:

- build-hosted-installation-assets.js: add a HOSTED_INSTALLER_FORBIDDEN_PATTERNS guard for install-qwen-standalone.ps1. The ps1 shim has no VERSION variable of its own (it forwards @args to the .bat), so the existing default-version positive-match patterns don't apply. The new guard fails the build if a $env:QWEN_INSTALL_VERSION assignment or a --version flag prepended to the forwarded argument list ever lands in the shim. Patterns are line-anchored with /m so the documented usage examples in the header docstring stay valid. Two vitest cases cover the reject and allow paths.

- install-qwen-standalone.sh / .bat: include the searched checksum-file path in the "SHA256SUMS not found" error. Operators triaging --archive failures could not tell from the prior message whether the fallback path (next to the archive) or the remote URL was being looked up. Existing test assertions updated to match the new wording.

Local validation: npm run test:scripts -> 160 passed | 9 skipped (was 158 | 9).

* fix: stamp release version in hosted installers and add Zip Slip protection [skip ci]

1. The hosted installation asset build now accepts --version and stamps it
   into the copied .sh/.bat installers so they default to the tagged release
   version instead of 'latest'. The release workflow passes the version.

2. install-qwen-with-source.bat now validates archive entries before calling
   Expand-Archive, rejecting paths with '..', leading '/', drive-rooted
   paths, empty names, or control characters — matching the protection
   already present in install-qwen-standalone.bat and the .sh installer.

* fix(installer): add SOURCE to PowerShell unsafe-character validation [skip ci]

The SOURCE variable is user-provided and used in path operations but was
not included in the :ValidateOptions unsafe-character check. Add it
alongside the other validated variables.

* fix: correct copyright year 2025 -> 2026 in new files [skip ci]

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
2026-05-21 11:57:10 +08:00
Shaojin Wen
16f0fde19a
fix(test): raise timeout for Windows installer end-to-end tests (#4352)
* fix(test): raise timeout for Windows installer end-to-end tests

The Windows-only end-to-end installer tests spawn cmd.exe to run the
.bat installer and then qwen.cmd --version, which boots a Node process.
On GitHub's windows-latest runners that chain regularly takes >5s, so
the default 5s vitest timeout makes them flaky (recently observed at
5804ms on CI). Bump the describe-block timeout to 30s, which leaves
headroom without masking real regressions.

* fix(test): raise timeout for Linux/macOS installer end-to-end tests

Match the timeout already applied to the Windows e2e block: the
Linux/macOS installer tests also spawn child processes via
execFileSync, so they share the same flake risk near the default 5s
vitest timeout. 15s leaves ample headroom without Windows' cmd.exe
overhead.

Addresses review feedback on #4352.
2026-05-20 17:42:29 +08:00
易良
cb7059f54d
feat(installer): add standalone archive installation (#3776)
* feat(installer): add standalone archive installation

* fix(installer): harden standalone archive installs

* fix(installer): address standalone review findings

* chore(installer): clarify review followups

* fix(installer): stabilize standalone script checks

* chore(installer): remove internal planning docs

* chore(installer): simplify standalone release review fixes

* test(installer): add Windows batch install smoke

* test(installer): fix Windows batch smoke quoting

* test(installer): preserve Windows cmd quotes

* fix(installer): use robust Windows checksum hashing

* ci: narrow installer debug matrix

* fix(installer): address standalone review hardening

* fix(installer): avoid Windows validation parse errors

* fix(installer): simplify Windows option validation

* fix(installer): harden standalone review fixes
2026-05-11 13:25:48 +08:00