Commit graph

8268 commits

Author SHA1 Message Date
Shaojin Wen
e76dff1c6b
feat(review): add declarative repository-context manifest (#8401)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (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
* feat(review): add OpenJDK repository context

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

* refactor(review): extract repository context foundation

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

* fix(review): repair CI type guard and add manifest repository context

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

* fix(review): harden repository context per maintainer review

Address both maintainer reviews on the repository-context PR:

- repo-context: a PR plan whose merge base never resolved
  (mergeBaseSha: null) now degrades to a null artifact without
  consulting the worktree, instead of throwing a misleading
  "invalid plan" error or falling back to the PR head.
- Identity reads return the same shape in PR and local modes
  (CRLF->LF, trimmed) and fail closed: absence yields null, a
  present-but-unreadable file throws.
- Context-required roles can no longer override the roster's
  effort, topology, and mode gates.
- The relatedPaths scan bound rises from 1024 visited entries to
  16384 and is documented, so honestly scoped manifests no longer
  abort reviews.
- A present-but-invalid repositoryContext now fails closed in every
  consumer; the gate no longer silently drops the disclosure.
- The duplicated validators and bounds are shared between the wire
  format and the manifest provider; the context role allow-list is
  derived from a single const; manifest arrays no longer require
  hand-sorting (uniqueness only).
- Nits: dead mkdir removed, output message names the provider,
  escape-message fix, unsafe changed paths skip instead of
  aborting, segment-glob regexes memoised, list helper hoisted.
- Docs: user-facing manifest section, trust-boundary residuals and
  foundation status in the design doc, fail-closed exit guidance in
  the skill.

* fix(review): skip unsafe related paths in manifest context (#8401)

* fix(ci): align review timeout helper test with externalized variables (#8401)

* fix(review): harden repository context bounds and base identity reads (#8401)

* fix(review): bound manifest matching work and pin round-2 review gaps (#8401)

* fix(test): isolate serve streaming suite from stray workspace settings (#8401)

* fix(review): cap identity reads, bill match work by length, pin round-3 gaps (#8401)

---------

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>
2026-08-06 15:13:31 +00:00
tlysanhuo
95655daf30
feat(cli): render inline terminal images (#8305)
* feat(cli): render inline terminal images

* fix(cli): preserve text around hidden citations

* fix(cli): clear pending image state on reset

* fix(cli): bound inline image rendering

* fix(cli): clear compacted image overflow markers

* docs(cli): clarify inline image resume scope

* fix(cli): address inline image review feedback

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-06 14:34:40 +00:00
Nothing Chan
79027bb2db
fix(web-shell): scope artifact actions to owning workspace (#8510)
* fix(web-shell): scope artifact actions to owning workspace

* fix(web-shell): preserve artifact authority in strict mode

* fix(web-shell): address artifact workspace review

* test(web-shell): cover missing file owner

* fix(web-shell): keep open artifact tabs alive across pane and list gaps
2026-08-06 14:29:30 +00:00
Edenman
02f1692d40
fix(web-shell): allow session refresh with daemon auth (#8445)
* fix(web-shell): allow session refresh with daemon auth

* test(web-shell): cover SPA fallback shell branch for non-session navigations

* test(web-shell): retarget sec-fetch SPA fallback test to non-session navigation

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

* fix(web-shell): serve pre-auth navigations during the deferred runtime window

The deferred-runtime gate applied bearerAuth to every non-bootstrap
request while the runtime was cold, so a browser refresh of
/session/<id> (and / and /assets/*) 401'd on the default
`qwen serve --token ...` start until something else warmed the runtime.

Exempt the same surface mountWebShellAssets registers before auth, via a
shared isPreAuthWebShellRequest predicate, so cold document navigations
start the runtime and load the shell while JSON fetches, API subpaths,
and --no-web daemons stay gated. The predicate is dynamically imported
to respect the serve fast-path import-boundary guards.

* fix(cli): align deferred pre-auth web shell gate with warm routing (#8445)

* fix(cli): report daemon startup failure to pre-auth web shell navigations (#8445)

A pre-auth-exempted Web Shell navigation that hit a failed deferred
runtime startup fell through to the bootstrap app's bearer gate and
received a misleading 401 instead of the 503 daemon_runtime_failed
envelope authenticated requests get for the same failure. Track the
exemption in the deferred dispatch and answer the diagnostic envelope
directly. Also cover the deferred gate's HEAD exemption, which was
previously untested.

* fix(cli): exempt bare /assets from the deferred pre-auth gate (#8445)

* refactor(cli): dedupe runtime failure envelopes and deferred-window test setup (#8445)

* refactor(cli): rename startup envelope helper and pin query-string deep links (#8445)

* fix(cli): serve the // root alias pre-auth and fail-close the deferred predicate (#8445)

* docs(cli): record the pre-auth %2F session deep-link invariant (#8445)

* fix(test): isolate serve streaming suite from stray workspace settings (#8445)

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

---------

Co-authored-by: 克竟 <dingbingzhi.dbz@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-06 14:23:10 +00:00
rockybot2026
732f4d8a29
feat(voice): support trusted private ASR base URLs (#8350)
* feat(voice): support trusted private ASR base URLs

* fix(voice): address private endpoint review findings

* test(voice): cover private endpoint edge cases

* test(voice): pin remaining endpoint edge cases

* fix(voice): address private endpoint review feedback

* fix(voice): clarify allowlist URL and normalize IPv6

* fix(voice): harden NAT64 address validation

* fix(voice): address managed endpoint review findings

* refactor(voice): extract shared IPv6 transition unwrap ladder (#8350)

Deduplicate the IPv6-transition unwrapping sequence (mapped, compatible,
NAT64, dotted-quad) that was repeated verbatim between isPrivateNetworkIp
and isAlwaysBlockedVoiceAddress on both CLI and Desktop surfaces. A single
unwrapIpv6TransitionStep helper now yields the next canonical address (or
'blocked' for unrecognized ::ffff: forms), and each predicate recurses
through it, preserving the exact re-check semantics at every unwrap level.

* test(voice): cover allowInsecureBaseUrl wiring through desktop default transports (#8350)

* fix(voice): add allowlist hint to private-network rejection error (#8350)

* fix(voice): reject always-blocked base URLs before offering the allowlist hint (#8350)

* fix(voice): resolve exact desktop voice provider before OAuth (#8350)

* fix(voice): address review feedback for trusted private base URLs (#8350)

* fix(voice): align desktop voice resolution with CLI semantics (#8350)

* fix(voice): scope desktop fail-closed resolution to policy-bearing entries (#8350)

* fix(voice): address round-8 review findings for trusted private base URLs (#8350)

Run the invasive process-global `mock.module('ws')` suite as
voice-ws-handler.isolated.ts so the desktop package's single-process
`bun test` run no longer leaks the fake socket into unrelated ws
consumers; the existing isolated loop runs it in its own process.

Shape-guard the desktop provider scan: non-object modelProviders
elements are skipped (falling through to OAuth instead of throwing a
raw TypeError), and non-string baseUrl/envKey/settings.env values on a
voice-model entry now surface the PROVIDER_ENTRY_REMEDY remediation
error instead of crashing.

Compute the DashScope-compatible /v1 rewrite before any allowlist
match in fromExactModelProvider so the stage-1 check, the remediation
messages, and the top-level recheck all compare the same final URL and
a single allowlist entry converges for split-horizon deployments.

Extend the CLI allowlist remediation messages to state which settings
scopes honor the entry, since serve mode never shows the interactive
workspace-strip warning. Thread providerProtocol through the CLI voice
model seams (createVoiceModelSource and the daemon buildModelsConfig)
so protocol-mapped custom provider groups resolve like the rest of the
CLI model surface, and document the remaining protocol-agnostic desktop
scan in the design doc. Correct the getHomeEnvFallback comment: it
adopts the narrower getHomeEnvFallbackVars candidate set on purpose.

Add multi-record DNS answer tests on both CLI and desktop net guards so
the records.some classification is pinned against the array shape
defaultLookupHost always produces in production.

* fix(voice): address round-9 review findings for trusted private base URLs (#8350)

* fix(voice): address round-10 review findings for trusted private base URLs (#8350)

* fix(voice): classify desktop voice duplicates before ambiguity check (#8350)

* fix(scripts): compare voice guard mirrors as parse trees (#8350)

---------

Co-authored-by: rockybot2026 <265985139+rockybot2026@users.noreply.github.com>
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: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-06 14:04:57 +00:00
Shaojin Wen
2221ea0325
fix(web-shell): Refresh sidebar branch chip immediately after checkout (#8600)
The sidebar git chip only polls every 60s, so after switching branches
through the branch picker it kept showing the old branch for up to a
minute — making the checkout look like it silently failed. Wire the
picker's branch-changed callback to an immediate status refetch,
matching the composer chip's realtime behavior.
2026-08-06 13:53:27 +00:00
Shaojin Wen
f4cf4268f5
fix(core): cap a streaming response's total lifetime, slim the review fan-out launch (#8602)
* fix(core): cap a streaming response's total lifetime

The stream inactivity watchdog resets on every chunk, so a drip-fed
stream — a gateway trickling keep-alive chunks, or a model crawling
through an oversized single message — kept it alive forever while the
message never completed: in issue #8597's CI review runs that meant
2.5-4.5 hours of silence ending in the outer timeout's kill.

Add a per-request lifetime cap (default 15 min) that does not reset on
chunk arrival. Tripping it throws a retryable ETIMEDOUT, so the
existing transport-continuation recovery resumes a healthy generation
the cap happened to cut. Config field streamMaxLifetimeMs; env knob
QWEN_STREAM_MAX_LIFETIME_MS; 0 disables.

* fix(review): carry findings lists as digest-named files, not inline

agent-prompt folded the cumulative findings list into every printed
verify/reverse-audit block. On a 12-14-auditor round that made the
launch one 65-82 KB assistant message — the oversized single message
whose stream generation never completed in #8597 — and cost a 5-10
minute paged relay through the orchestrator's context before every
round.

The list now goes where the brief already goes: on disk, named by the
same findings digest that keys the record; the block carries a read_file
pointer. The delivery guarantee is unchanged — a launch that drops the
read matches no record — and the retirement scheduler's echo guard
reads the list back from the file the prompt names, failing toward
auditing when it is gone.

* fix: address review feedback on stream guards and findings files

* fix: address review feedback on findings delivery and stream guards

The delivery floor counted only the brief's read receipt, so an
instruction-skipping verifier could open its brief, skip the one
instructed read of the findings file its block points at, and clear
the gate having never seen the list it ruled on. The floor now
extracts the findings pointer from the recorded prompt and requires a
successful read of it — a new findings-unread delivery shape, with gap
texts for the verify, reverse-audit, and combined steps. A failed
findings-file write also speaks on stderr instead of silently pointing
a whole round at a missing file.

Stream guards: consult the lifetime deadline at the top of the loop,
not only when the timer wins the race — a pre-buffered chunk resolves
next() as a microtask and beats setTimeout(…, 0) every time. Merge the
two ETIMEDOUT bypass blocks into one rule. New tests pin the
idle-off + lifetime-on branch, the both-guards-0 disable (config and
env), and the buffered-source shape.

Also reword the prose sites that still described the inlined list.

* fix(review): name the findings file in the verify/reverse-audit briefs

The two briefs still told the agent the findings were "listed in the
message that launched you", but since the list moved behind a
read_file pointer they find only a pointer there. The agent reads its
brief first (the block mandates it), so the stale text sent it looking
for finding blocks in the launch message — the exact findings-unread
shape the new delivery floor gates on. Point both briefs at the
.findings.md file and the required read instead.

* fix(review): address round-3 review — empty-round brief, guard hardening

The b796708 brief fix stated unconditionally that the launch message
points at a findings file, but an early reverse-audit round on a clean
review names no file (the empty branch prints '## Nothing is confirmed
yet' with no pointer) — the auditors were being sent to read a path
their prompt does not contain. The reverse-audit brief now says the
file is named when there is a list, and that an early round has none.

Also from the round-3 review:
- writeFindingsFile's failure diagnostic now uses writeStderrLineSafe:
  the catch exists to keep the build alive, so it must not throw out of
  it on EPIPE (qwen … | head).
- The retirement echo-guard resolves the findings pointer from the
  CLI's own record (never the orchestrator's pasted copy), confined to
  the plan's record dir, memoized per round — an out-of-bounds or
  unreadable file degrades to the prompt, failing toward auditing.
- Document the delivery floor's deliberate weakening: it proves the
  findings file was opened, not paged to completion (coverage.ts), and
  drop the last stale 'record folds the findings in' comment.
- withStreamInactivityTimeout returns the source untouched when both
  guards are disabled, so the invariant survives a caller refactor
  (setTimeout(Infinity) would otherwise clamp to ~1ms).
- settings.md + the config comment now say the stream guards are
  OpenAI-compatible-only, and that the 15-minute cap bounds a stream
  whose idle timeout was raised above it (raise or disable the cap to
  keep a longer window).

* fix(review): address round-4 review — memo hygiene, read-visibility, naming

The retirement echo-guard memoized a failed read's per-record fallback
under the round's shared findings pointer, so one chunk's launch prompt
would serve as a sibling chunk's findings list. Memoize only successful
reads; a miss falls back to THIS record's own prompt, uncached.

The findings block now prints the list's line count, so an agent whose
read_file truncates can see it saw a fraction rather than the whole
confirmed list — a visibility aid the delivery floor (which proves the
file was opened, not paged) does not provide.

Also from the round-4 review: rename withStreamInactivityTimeout to
withStreamGuards (it now enforces two guards), correct the constants
comment (the cap is measured from the stream's first iteration, not
its first byte), and give the two stream-guard knobs a dedicated
settings.md entry instead of burying them in the timeout paragraph.

* fix(review): address round-5 review — deterministic record walk, accurate line count

The cross-contamination regression test only discriminated under one
readdir order: readRecordedPrompts walked the record dir in filesystem
(filename-hash) order, so which chunk's fallback poisoned the shared
memo depended on the walk. Sort the directory listing — a deterministic
walk removes that whole class of order-sensitivity from a module that
reasons per-record, not just from this test (a temporary revert of the
memo fix confirms the test now fails against the buggy shape).

findingsSection printed the line count of the TRIMMED findings body
while writeFindingsFile writes the untrimmed content, so a list with
leading/trailing blank lines got a label smaller than the file the
agent actually reads — precisely the under-reading the count exists to
make visible. Count the untrimmed content.

* fix(core,review): charge the lifetime cap on upstream-wait, not delivery time

Round-2's top-of-loop deadline check made the lifetime cap measure
end-to-end delivery time: a healthy upstream that finished and buffered
its chunks was cut for the CONSUMER's slowness (a paused IDE client, a
big render), and a stream whose terminal done resolved at the boundary
was converted into a retry. The cap is now charged on accumulated
upstream-wait — the time the loop is blocked in await it.next() — so a
buffered, already-complete stream always completes and only real
upstream latency counts; the drip-fed never-completing stream spends
exactly that time waiting, so #8597's shape is still caught.

Also from the round-6 review:
- Hoist the stream-guard error branch above the thinking-tag check: a
  drip-fed gateway cutting mid-<think> surfaced the guard's ETIMEDOUT
  as a PROTOCOL_TAG_LEAK and burned the tag-leak retry budget.
- The findings line count drops the trailing newline's empty segment,
  so a 12-line list is not advertised as 13.
- settings.md: the stream guards are env/config-only (no settings.json
  key); document that streamIdleTimeoutMs: 0 embedders now also need
  streamMaxLifetimeMs: 0 to fully opt out.
- constants: a functionCall already streamed (the tool-heavy common
  case) recovers as a visible classified error, not a continuation.
- coverage.ts: drop the stale 'four shapes' counts after Delivery grew
  a fifth; pipeline: dedupe the instanceof in the guard debug log; and
  correct the both-guards-off test's comment to pin the outcome, not
  the caller mechanism.

* fix(core,review): monotonic guard clock, findings-write fallback, read-only floor

Round-6 follow-ups the previous commit left open:

- The stream guards accounted on Date.now(): an NTP step forward (or a
  long sleep) killed a healthy generation on the next iteration, and a
  backward step silently disabled the lifetime cap — the hang #8597
  exists to bound. All guard accounting is now performance.now(); the
  setTimeout it races is monotonic too, so the two agree. Vitest fakes
  performance alongside the timers, and a new test A/B-verified against
  the wall-clock shape (it fails on Date.now() accounting).

- A failed findings-file write returned the path anyway, pointing a
  whole 12-14-agent round at a file that does not exist — every agent
  burned its round, then the delivery floor failed it. writeFindingsFile
  now returns null on failure and findingsSection falls back to inlining
  the list (the pre-#8597 shape): the recorded prompt carries the list,
  the delivery check compares it verbatim, and the build stays alive.

- The findings delivery floor matched the path in ANY successful tool
  call's serialized args, so a search_file_content or list_directory
  that merely named the file cleared it without reading a line. The
  transcript parser now records read_file calls apart, and the floor
  counts only those — a mention is not an open.

* fix(core,review): round-7 leftovers — upstream-wait wording, key helper, test mock

The round-6 error message and class doc still said 'total lifetime cap'
and promised the continuation recovery unconditionally, and the
wrap-site comment still read 'aborts at maxLifetimeMs from stream
start' — all three now describe the upstream-wait semantics the guard
actually implements, and the message names the wall clock separately so
the two numbers reconcile.

The round suffix baked into findings-role record keys and the findings
file name was derived independently in three sites (findingsFileFor,
runAllChunks, the single build) — a change to how a round is spelled
would update two of three and silently fork the artifacts; roundPartOf
spells it once.

The writeFindingsFile test's module mock replaced all of stdioHelpers
(three stubs, missing the other exports) and asserted mock.calls[0][0]
with no mock reset — it now spreads importOriginal, clears mocks in
beforeEach, and matches the stderr lines with stringContaining.

* fix(review,core): round-3 follow-ups — conditional findings briefs, JSDoc placement, log labels

The verify/reverse-audit briefs told the agent its findings live in the
.findings.md file unconditionally, on the exact path (writeFindingsFile
returning null -> findingsSection inlining the list) this PR added to
work WITHOUT one; both now say 'when the message points at a findings
file, read it; on the rare write-failure fallback the list is inlined
in the message, read it there.'

roundPartOf had been inserted between findingsFileFor's JSDoc and the
function, orphaning the comment that records the one-file-per-round
and null-means-inline contracts; the helper now sits above it.

The guard debug log labelled the wall clock 'streamLifetimeMs' beside
the cap; it is now 'wallClockMs' so the two numbers reconcile the same
way the error message does.

* fix(review): anchor the findings-pointer extraction to its emitted shape

findingsPointerOf matched the FIRST read_file(file_path="….findings.md")
anywhere in the recorded prompt. On the write-failure inline fallback the
findings list occupies exactly the position the pointer would sit in, and a
finding entry there can itself quote a read_file pointer of its own (a
finding about this pipeline, which the harness produces when it reviews this
repo). The loose match then extracted the quotation as the pointer and the
readers diverged: coverage demanded a read of a path no agent was told to
read (a spurious findings-unread on a run that is already degraded), and
retirement, worse, confined-and-read an earlier round's file and flipped a
just-filed finding to an echo, retiring a chunk that had just reported — the
one direction the module's header commits to never failing. A quoted pointer
inside a findings entry is indented or embedded in prose, so anchoring to a
standalone read_file line removes it; the happy-path pointer is alone on its
own line inside its fence. A test drives an inlined list containing a
pointer-shaped line (fails under the old loose regex).

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-06 13:27:30 +00:00
Shaojin Wen
44747cc27b
docs: add legacy code audit (/audit) design doc (#8397)
* docs: add legacy code audit (/audit) design doc

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

* docs: revise legacy audit design with round-2 replication evidence

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

* docs: note cross-file tracer cost and budget rule in legacy audit design

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

* docs: address review feedback on legacy audit design (#8397)

* docs: wire invariant triple, personas, and event detection into audit design (#8397)

* docs: address round-3 review feedback on legacy audit design (#8397)

* docs: address round-4 review feedback on legacy audit design (#8397)

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

* docs: address round-5 review feedback on legacy audit design (#8397)

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

* docs: address round-6 review feedback on legacy audit design (#8397)

* docs: address round-7 review feedback on legacy audit design (#8397)

* docs: address round-8 review feedback on legacy audit design (#8397)

* docs: address round-9 review feedback on legacy audit design (#8397)

* docs: address round-10 review feedback on legacy audit design (#8397)

* docs: address round-11 review feedback on legacy audit design (#8397)

* docs: address round-12 review feedback on legacy audit design (#8397)

* docs: address round-13 review feedback on legacy audit design (#8397)

* docs: address round-14 review feedback on legacy audit design (#8397)

* docs: address round-15 review feedback on legacy audit design (#8397)

* docs: address round-16 review feedback on legacy audit design (#8397)

* docs: address round-17 review feedback on legacy audit design (#8397)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-06 13:15:12 +00:00
qqqys
41f9b83145
fix(core): remove fixed Goal continuation limit (#8421)
* fix(core): remove fixed Goal continuation limit

* test(core): name former Goal continuation limit in regression guards

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-06 12:29:25 +00:00
jinye
2394afa797
fix(core): resolve symlinks before granting read permission (#8636)
The read permission check classified a path lexically, so a symlink
planted inside an always-allow root returned arbitrary host files with
no confirmation prompt. Canonicalize the candidate before the
containment test, and the roots alongside it where doing so cannot be
steered by repo-tracked contents.

Refs #8635
2026-08-06 12:11:55 +00:00
Shaojin Wen
35108c0cf0
ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback (#8603)
* ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback

The heavy autofix jobs (issue-autofix, build-cli, review-address) were
pinned to GitHub-hosted runners, where they hold up to five 300-minute
slots per scan. Combined with CI, that saturates the org's ~20 hosted
concurrency slots and starves the PR-review workflow's hosted-only jobs
(delay timer, fork authorize, /resolve).

Route the three heavy jobs to the persistent ecs-qwen pool using the same
repository-guarded expression as the rest of the fleet, with an
ubuntu-latest fallback for forks of this repo and for
MAINTAINER_ECS_RUNNER_DISABLED.

This is safe for the fleet's two secret-bearing agent jobs:
- Every review-address target is live-gated to write+ (internal) authors
  at scan AND address time; in-repo PR heads already require push access.
- CI_DEV_BOT_PAT is a write-level branch-push token with no privilege
  escalation (cannot bypass branch protection).
- The ECS pool ships docker, and repo-hygiene already runs the sandbox
  image there in production; sandbox containers use --rm with no fixed
  name, so concurrent legs on one machine do not collide.

Relax the 'Check runner environment' gate to accept self-hosted, and drop
the github-hosted condition on 'Set up Node.js' so Node 22 is provisioned
on ECS. The contract test is updated to pin the new ECS routing expression
(including the repository guard and hosted fallback) instead of the old
hosted-only one.

* ci(autofix): harden ECS-routed heavy jobs for the shared persistent pool

- Restore workspace ownership before checkout in issue-autofix,
  build-cli, and review-address, matching the sibling pool workflows
  (root-owned leftovers from containerised jobs break checkout/npm ci).
- Sanitize the reused workspace's local git config (allowlisted
  unset-all + symlink-aware hook sweep, qwen-triage's pattern) before
  the PAT-bearing git steps in issue-autofix and review-address.
- Give issue-autofix a per-run WORKDIR (/tmp/autofix-<run_id>): pool
  registrations share one /tmp and issue-phase runs never serialize.
- Drop stale local autofix/issue-* branches after checkout so a retry
  on the same registration cannot hit "branch already exists" or push
  a failed attempt's stale commits.
- Extend the ECS routing guard for PR-family events (same-repo head or
  write+ author), mirroring ci.yml's classify_pr convention, and fix
  the runs-on comments that claimed fork PRs already fell back.
- Stop persisting CI_DEV_BOT_PAT into the origin remote URL: publish
  pushes authenticate transiently, and the redundant set-url in
  'Push and report' is removed (push/fetch already use a URL inline).
- Pin all of the above in the workflow contract tests, reusing hoisted
  job-block constants.

* ci(autofix): harden ECS pool hygiene with shared hardened sanitize action

Review round for the ECS routing of the heavy autofix jobs. The
sanitize step shipped in the previous commit was the trusted-lane
variant of the qwen-triage sweep, which two verified bypasses defeat
(worktree-scoped config carrying core.hooksPath, and a global
core.hooksPath steering the hook deletion). Port the hardened variant
into a composite action so all three heavy jobs share one copy, and
add it to build-cli, whose checkout runs in the same reused workspace
and feeds the bundle every PAT-bearing leg executes.

Also completes the persistent-pool hygiene the routing depends on:
per-run/per-target WORKDIRs now chmod 700 at creation and are removed
by an always() teardown step after the artifact upload (nothing else
ever deleted them); the stale-branch sweep detaches HEAD first and
globs via BRANCH_PREFIX; the artifact upload reads env.WORKDIR
instead of a duplicated literal.

The tautological runner-environment gate is repurposed as a
fail-fast docker preflight (a missing daemon otherwise surfaces only
at 'Resolve sandbox image', after npm ci/build), and Install tmux
switches to sudo -n so hosts without passwordless sudo fail with a
clear message instead of dying on a prompt.

The runs-on comments are rewritten to say exactly what they guard:
which events carry the same-repo/write+ clause and which rely on the
live write+ or label gates, why review-address is an author-permission
gate rather than a head-repository gate, and where the pool's docker
availability is proven in-repo (qwen-triage's container jobs on the
same labels).

Contract tests pin all of it: hygiene steps present and ordered
before checkout in every heavy job, the hardened sweep's ordering and
containment in the shared action, the docker preflight and tmux
hardening, the WORKDIR lifecycle, hosted-only short jobs, and generic
next-job slice boundaries so a job inserted after review-address can
no longer hide inside its test slice.

* ci(autofix): inline git-config sanitize step before checkout

The composite sanitize action was invoked as a local action
(`uses: './.github/actions/sanitize-workspace-git-config'`) BEFORE
`actions/checkout` in all three ECS-routed jobs. A local action resolves
from $GITHUB_WORKSPACE, so:

- on any clean workspace it hard-fails with "Can't find 'action.yml'" —
  every hosted-fallback run (forks, MAINTAINER_ECS_RUNNER_DISABLED=true,
  i.e. the documented rollback path) and any freshly registered ECS
  runner — and it cannot self-heal, dying before checkout.
- on a reused workspace it executes the leftover copy, which for
  review-address is the previous run's PR branch — pre-checkout execution
  of PR-controlled content, the exact vector the step exists to prevent.

Inline the hardened sanitize script as a plain `run:` step in each job
(the qwen-triage 'Clean stale agent state' pattern) and delete the
now-consumer-less composite action. Update the contract test, which had
pinned the broken ordering, to pin the inline run step instead, and add a
generic guard that no local action may precede a checkout.

* ci(autofix): harden PAT handling and pool isolation on the shared pool

Address the Medium findings from the ECS-routing review:

- PAT pushes (Publish PR, Push and report, and the fork dry-run
  preflight) now authenticate via a transient credential helper instead of
  a token-bearing remote URL. The token enters neither the reused
  workspace's .git/config nor the process argv, which is world-readable
  via /proc on this shared host; the helper config holds only a
  ${GITHUB_TOKEN} reference and is swept by the next sanitize pass.
- 'Check runner environment' now asserts the self-hosted runner is an
  ecs-qwen pool member by name, so a mis-labelled registration cannot
  silently claim a PAT-bearing 300-minute job.
- 'Reset autofix workspace' age-sweeps abandoned /tmp/autofix-* dirs (a
  hard runner kill skips the always() teardown and run_id never repeats)
  and prunes the reused workspace's .git.

The contract test is updated to pin the tokenless push/fetch URLs, the
credential-helper authentication, and the absence of a token-bearing URL.

* ci(autofix): create autofix workdirs with umask 077 and correct cleanup comments

* ci(autofix): scope PAT credential helper to a one-shot git -c, plus review fixes

Address the latest review round on the ECS-routed heavy jobs.

High — the transient credential helper was not transient: `git config
--local credential.helper` wrote into the reused workspace's .git/config
and the matching unset was skipped on every error path (bash -e abort,
the fork-preflight exit 0, the salvage-loop exit 1s), stranding a helper
that answers any host with the PAT where ci.yml / pr-review jobs could
read it. All three push paths now use a one-shot, host-scoped
`git -c credential."https://github.com".helper=...` (a git_auth wrapper
for the salvage loop), so nothing is ever written to .git/config.

Medium — narrow the sanitize allowlist's `remote.*` to
`remote.<n>.(url|fetch|pushurl)` (uploadpack/receivepack are command
strings the sweep previously kept), and cover all three inlined sanitize
copies in the hardening test with a byte-identical pin (previously one
of three), rewording the stale "composite action" references.

Low — drop the recursive `rm -rf` hooks fallback that could wipe the git
dir itself (warn instead), extend the /tmp sweep glob to the legacy fixed
dir (`autofix*`), and make `git gc` threshold-driven (`--auto --prune=now`).

The contract test pins the git -c / git_auth form, the absence of any
config-written credential helper, and the tokenless URLs throughout.

* ci(autofix): pin gc to the foreground and anchor push auth assertions

Round-2 review follow-ups:

- `git gc --auto --prune=now` detaches by default (gc.autoDetach=true),
  and --prune=now is unsafe when other processes write concurrently —
  exactly what follows in these 300-minute jobs. Force it synchronous
  with `-c gc.autoDetach=false` so the repack still only fires when the
  --auto thresholds say so, but never races the checkout, agent commits,
  or merge/retry loop that come after it.
- Tighten the two push assertions (dry-run preflight, Publish PR) to
  require the host-scoped `git -c credential."https://github.com".helper`
  prefix immediately before the push, instead of a bare
  `push --no-verify …` match that a `git push` regression would still
  satisfy.

---------

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>
2026-08-06 07:19:32 +00:00
Shaojin Wen
2c514b50b9
fix(autofix): serialize scan-and-pick issue runs in one concurrency group (#8435)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(autofix): serialize scan-and-pick issue runs in one concurrency group

The issue-phase concurrency group fell back to github.run_id for every
run that route did not force a target — which is every scheduled run and
every label-triggered run. Run-unique groups exclude nothing, so two
overlapping scans (cron effectively fires every 40-70 minutes while the
job may run for up to 180) could both pass the live label recheck during
the minutes-long assess call, double-claim the same issue, and burn two
multi-hour agent runs on it; the loser then fails its push and posts a
withdraw comment.

Key forced dispatches per issue, label events on the payload issue, and
all scan-and-pick runs (cron or unforced dispatch) on one shared
'scheduled' group. cancel-in-progress stays false so a superseded tick
still runs when targets remain.

* fix(autofix): make concurrency group pin prettier-stable (#8435)

* fix(tests): sync qwen-resolve-workflow timeout pins with repository variables (#8435)

* fix(ci): pin issue-phase concurrency group equal to FORCED_ISSUE (#8435)

* test(ci): anchor right edge of issue-phase concurrency group pin (#8435)

* fix(autofix): keep never-runnable runs out of the issue-phase concurrency group (#8435)

* test(autofix): pin issue-autofix concurrency gate to the job if predicate (#8435)

* fix(autofix): exclude dry runs from the issue-phase concurrency groups (#8435)

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

* test(autofix): pin the Claim/Publish dry-run step gates (#8435)

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

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

---------

Co-authored-by: verify <verify@local>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-06 05:27:39 +00:00
Harjoth Khara
ec4f1e02e4
fix(cli): probe sandbox runtime before selecting it (#7734)
* fix(cli): probe sandbox runtime before selecting it

Sandbox selection treated PATH presence as proof a runtime works, so an
installed-but-unusable docker (daemon stopped, socket unreachable, user not in
the docker group) was still selected and the podman branch below it became
unreachable. Each candidate is now probed with `version` — the cheapest command
that still contacts the daemon — and the first one that actually runs wins.

When nothing usable is found, the error names the runtime that broke and quotes
its failure instead of claiming nothing is installed. An explicit QWEN_SANDBOX
choice is never silently redirected; it fails with the daemon error attached.
sandbox-exec is not probed, being a kernel facility rather than a daemon client.

Fixes #7732

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): attribute the sandbox command to its real source

The probe failure hardcoded "(from QWEN_SANDBOX)", but an explicitly named
command also arrives from --sandbox or tools.sandbox in settings. Naming the
env var unconditionally sends a user who never set it looking in the wrong
place — the same misdirection this change set out to remove.

The parenthetical is now emitted only when the env var actually supplied the
value, which also corrects the pre-existing "Missing sandbox command" message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): apply source-accurate attribution to the auto-detect errors too

The explicit-string path stopped hardcoding QWEN_SANDBOX, but the auto-detect
errors still did, so `qwen --sandbox` with a broken runtime pointed at an env
var the user never set. Both auto-detect messages now name the env var only
when it was what enabled sandboxing, and otherwise suggest --sandbox.

Also lowers the probe timeout from 10s to 5s. Probes run sequentially, so the
ceiling is paid once per wedged runtime; a healthy `docker version` answers in
roughly 200-500ms, so 5s keeps an order of magnitude of headroom while halving
the worst-case startup delay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(cli): assert the sandbox probe argv and timeout

The spawnSync stub routed on command name alone, so the probe arguments
were never observed. Rewriting the probe to `docker --version` — which
prints the client build without contacting the daemon, restoring the
original defect — left all 12 tests green, as did deleting the timeout
that bounds a wedged daemon.

Validate both in the stub, so every probing test carries the check, and
pin the argv at the fallback call site where the behavior is asserted.

Reported by @wenshao in the mutation matrix on #7734 (M9, M8).

* test(cli): cover the empty-output and timeout probe branches

Two probe branches in probeSandboxCommand were unpinned, so a mutant in
either survived the whole suite:

- a non-zero exit with empty output relied on the synthesized-message
  fallback; dropping it made the probe return undefined and a broken
  runtime read as usable
- the result.error branch that the probe timeout produces had no test;
  deleting it degraded the error text with the suite still green

Each new test fails against its mutant and passes on the real code.

Reported by @qwen-code /review on #7734.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(cli): isolate the sandbox probe in config precedence tests

The image-precedence tests enable the sandbox and assert only which image
wins. Since the runtime probe added here spawns a real `docker version`
subprocess, and this file mocks command-exists but not child_process, the
probe runs for real. On macOS the sandbox-exec branch returns before probing,
so it passed there and on CI runners that have docker; on any other host
without a running daemon getSandboxCommand throws and all four tests fail on
image assertions they never reach.

Mock the docker/podman `version` probe to report healthy, so selection is
deterministic and the tests exercise image precedence on every platform.
Every other spawnSync call stays real.

Reported by @qwen-code /review on #7734.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): cache the sandbox probe and sanitize its error output

loadSandboxConfig runs twice on a sandboxed startup, so every candidate was
probed twice — the wedged-docker-then-podman fallback paid the 5s cap twice
(~10s), which the PR description wrongly called "once per wedged runtime".
Cache each command's probe outcome per process (with a test-only reset),
mirroring the ripgrep health cache, so a runtime is contacted at most once.

The probe also returned the runtime's stderr verbatim into FatalSandboxError
messages, carrying ANSI/control bytes to the terminal. Strip them with the
existing stripAnsiAndControl helper, whose own doc names this case.

Both requested by @wenshao in the review on #7734 (items 1 and 3).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): keep an all-control-character probe failure from reading as usable

The sanitizer checked the failure line for emptiness before stripping, so a
runtime whose stderr is only escape/control bytes stripped to '' — falsy — and
the broken runtime was selected as usable, reintroducing the presence-vs-
liveness bug through the sanitizer. Check emptiness after stripping and fall
back to the synthesized message.

Also drop the redundant `candidate !== 'sandbox-exec'` guard (sandbox-exec is
only a candidate once its presence is confirmed), reword the all-broken hint to
"try another installed runtime" since another may be installed but also broken,
and add tests pinning the control-character failure and the first-of-several-
broken diagnosis.

Reported by @qwen-code /review on #7734.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 05:17:01 +00:00
顾盼
8fd0162c68
fix(release): normalize Live Host signing identity (#8579) 2026-08-06 02:29:38 +00:00
Shaojin Wen
89b3d5ea8e
fix(autofix): ship core dist in the review CLI bundle (#8612)
* fix(autofix): ship core dist in the review CLI bundle

The review fan-out (#8548) dropped the per-leg build: legs restore the
shared bundle's repo-root dist/ and rely on the verify gate's full
build for branch verification. But the gate's settings-schema and i18n
checks run BEFORE any build, on every path including no-action, and
their tsx-transpiled cli sources import '@qwen-code/qwen-code-core',
which resolves through the workspace symlink to
packages/core/dist/index.js. With no build on the leg, the generator
crashes with ERR_MODULE_NOT_FOUND and the gate misreports a
deterministic "settings schema is stale" rejection (run 31031063525 on
PR 8600), then burns an 18-minute repair agent round on an environment
problem no agent can fix.

Ship packages/core/dist (+~8.5MB gzipped) alongside the root dist/ in
the fan-out artifact and assert its entry point on restore. This
restores exactly the pre-fan-out state: legs used to build the trusted
base themselves before the branch checkout, so the gate always ran
against base-built core dist. The workflow contract tests pin the new
tar command and the restore-side assertion.

* fix(autofix): rebuild branch-touched core dist before the schema gate

Review feedback on the core-dist bundle fix:

- Rebuild packages/core from branch sources in the review verify gate
  when the branch diff touches core's sources, so the pre-build
  settings-schema check never compares the branch's committed schema
  against a base-built dist (changed runtime constants) or crashes the
  generator (changed exports). Lives in the shared gate script so both
  the initial and the repair gate are covered.
- Narrow the bundle/restore comments and their test mirror to the
  settings-schema generator: the i18n check resolves core to sources
  via the packages/cli tsconfig paths map and needs no dist (verified
  empirically).
- Anchor the tar contract pin at end-of-line so additive path drift
  fails the suite instead of passing on a substring match.
2026-08-06 01:22:02 +00:00
Shaojin Wen
cb3dc107f7
test(core): deflake glob external-path test — dedicated empty dir, not /tmp (#8604)
* test(core): glob external-path test uses a dedicated empty dir, not /tmp

The test really walks the path it names; on loaded self-hosted runners
a littered /tmp made it time out at 15s — three times across #8388's
CI runs alone, each reddening the required Test check for a test whose
claim is only 'no workspace-boundary refusal'. An empty mkdtemp dir
outside the workspace proves that claim deterministically.

* test(core): apply review nits to glob external-path test (#8604)

Address review feedback:
- Use the same `glob-external-` mkdtemp prefix as the neighbouring
  external-dir test.
- Assert `result.error` is undefined so an unrelated glob failure
  cannot slide past the negative workspace assertion.
- Shorten the CI-narrative comment; the flake evidence lives here
  instead: run 31044530774, job 92443611234 (#8388).

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-06 00:03:22 +00:00
易良
3235faf418
ci: add hk ECS runners to update matrix (#8599)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
npm cache producer / Save npm cache (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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
Update ECS Runner Qwen / Update Qwen on ecs-update-64c (push) Waiting to run
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-1 (push) Waiting to run
Update ECS Runner Qwen / Update Qwen on ecs-update-sg (push) Waiting to run
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-2 (push) Waiting to run
2026-08-05 16:57:08 +00:00
qwen-code-ci-bot
5173052e37
chore(release): v0.21.6 (#8598)
* chore(release): v0.21.6

* docs(changelog): sync for v0.21.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-05 16:11:17 +00:00
jinye
2ad72fd5e9
fix(cli): Bound ACP textual tool-result payloads (#8450)
* fix(cli): Bound ACP textual tool-result payloads

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

* fix(cli): Harden ACP text projection budgets

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

* perf(cli): Cap multi-block ACP projection scans

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

* test(cli): Cover ACP projection boundary guards

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 15:10:53 +00:00
Shaojin Wen
86a33777b3
feat(review): test the diff's reverse-dependency closure with disclosed caveats (#8490)
* feat(review): test the diff's reverse-dependency closure, fail open to the full suite

build-test tested only the workspaces the diff changed. That under-tests in
the one way a compile cannot catch: a behaviour change in core leaves every
dependent compiling and still fails their suites, so the break surfaces in a
consumer's tests or nowhere. The test phase now runs the changed workspaces
plus their transitive dependents (dependencies/devDependencies edges, the
same declared graph the build set already walks) - still a subset of the
build set, so every tested package was built with what it compiles against.

Scoping is an optimisation over the full suite, and every input that would
make the scoped set unsound now falls back to the repo's own full-suite
command (one root `npm test`; per-workspace suites when the root defines no
test script), never silently:

- a changed file outside every workspace (root scripts/, .github/, docs):
  the test scripts themselves live in the root package.json, so no
  per-workspace subset covers such a change;
- a workspace whose package.json does not parse: it is invisible to the
  dependency graph, so the closure may be missing a real dependent;
- a closure past half the workspaces: not a meaningful narrowing, and the
  root command brings the repo's own parallelism.

The report discloses the decision either way in a new `testScope` field
({ mode: "workspaces", workspaces: [...] } | { mode: "full", reason }), and
the ok-note names the scoped suites, so Agent 7 can state exactly what was
and was not run instead of implying the whole suite passed. Agent 7's brief
is updated to read it. Single-package repos keep a byte-identical report -
their one suite is their full suite, and no scoping decision happened.

On this monorepo the closure keeps the common cases scoped: a cli-only diff
runs 1 of 22 suites, a core diff runs its 7-package reverse closure.

The new lib/workspace-scope.ts carries the pure decision (resolveTestScope,
unparseableWorkspaceManifests); the reverse closure is extracted from
buildSetFor into reverseDependencyClosure so the built set and the tested
set cannot drift apart.

* fix(review): keep inert prose from forcing the full-suite fallback

The out-of-workspace fail-open was too blunt: it sent a README or LICENSE
edit to the full suite, spending minutes measuring nothing - prose cannot
fail a test, and "run everything" for a docs PR is the exact waste test
scoping exists to remove.

The scope decision now distinguishes the two kinds of outside file:

- inert prose (what the plan pipeline's own classifyPath calls docs, plus
  the extensionless license family: LICENSE, COPYING, NOTICE and variants)
  neither forces the full suite nor widens a scoped set. A diff of prose
  alone obliges no test at all - the pre-scoping "nothing to run, and that
  is a complete answer" behaviour, restored; prose riding along with one
  workspace leaves the run scoped to that workspace.
- influential files (scripts/, .github/ workflows, root package.json and
  other root config) still fail open to the full suite with the reason
  recorded - the root package.json defines the test scripts themselves.

Reusing classifyPath rather than a second docs definition means the two
cannot drift, and inherits its safe bias: markdown outside the docs dirs
and the repo root stays influential (in-tree markdown can be executable
behaviour), erring toward running MORE tests. LICENSE.js stays influential
too - a name is only inert when nothing executes it.

* fix(cli): run the scoped closure with caveats instead of a full-suite fallback (#8490)

The full-suite fallback ran the repo root npm test, which cannot finish
inside a command deadline on a large monorepo (31 minutes in this repo's
CI against a 300s deadline) and fired for about a third of diffs, trading
a working scoped signal for a guaranteed timeout. Replace it: the scoped
set always runs, and every input that makes it possibly incomplete is
disclosed as a caveat on testScope instead.

Also close the silent-scope gaps the review found: manifests that parse
but lack a usable name now count as unreadable graph entries; symlinked
workspace members are visible to the graph; optionalDependencies are
reverse edges; a root suite that declares a workspace dependency runs as
a dependent; negation-excluded members earn no caveat; the half cap
counts testable suites only; build-only probes report no testScope; and
the docs-classified inert carve-out is gone (root AGENTS.md is load-
bearing for packages/cli's own suite) — only the license family stays
inert.

* fix(cli): address review round 2 on scoped build/test honesty

Resolve the round-2 findings on the scoped closure work:

- testScope is attached only once the scope executed; every early return
  that ran zero test commands no longer carries it (R2-1).
- The more-than-half cap counts a participating root suite on both sides
  (R2-2), and its witness is pinned in both directions (R2-13/24).
- Manifests whose body is the JSON literal `null` classify as skipped
  instead of throwing past the try/catch (R2-3); test-plan honours the
  skipped signal rather than ruling a false absence (R2-5).
- The single-root note no longer claims tests ran when the root defines
  none (R2-11), and the scoped note no longer claims build-only
  dependents were tested (R2-21).
- A negation only excludes a file when it excludes the owning member, so
  a partial negation keeps the member's suite visible (R2-7); a
  glob-claimed dir with no manifest self-discloses (R2-20).
- The build set and test closure are computed over one root-inclusive
  graph so a member that depends on the root is built before it is
  tested (R2-23); the dead single-root skipped reset is removed (R2-9).
- peerDependencies gains a witness (R2-17), the skipped docblock records
  the per-shape truth (R2-18), and caveat substance is asserted in the
  note, not just the label (R2-15).

* fix(cli): address review round 3 on scoped build/test graph honesty

Round-3 Critical findings, all probe-verified against the tree:

- R3-1: the build-only (merge-base probe) path excluded the root from the
  scope graph, so the probe measured a different build set than the run it
  baselines. The graph is now identical for probe and full run.
- R3-2: a root that joins the closure as a dependent ran `npm test` at the
  root — which for a `--workspaces` fan-out script repeats the ENTIRE suite
  inside one command deadline, the fallback this module refuses. A fan-out
  root now leaves the executed set with a caveat; it stays in the graph.
- R3-3: root participation was gated on a TEST script, so a build-only root
  vanished as a graph node and every dependent reached through the root's
  name was silently dropped. The root now joins the graph whenever it is a
  package with a build or test script; the dir->package map is built from
  the scope graph so the root's own build runs too.
- R3-5: test-plan ruled `contradicted` ("script does not exist") from a
  table that was silently incomplete for unmodeled globs (`packages/**`) and
  `./`-prefixed globs. Unmodeled layouts now rule `unchecked`, and `./` is
  normalized everywhere the walker reads globs.
- R3-6: a literal member listed BEFORE a `*` claiming its parent segment was
  silently dropped (npm includes it under either order). It now lands in
  `skipped`, so the broken-graph caveat fires and names it.
- R3-7: a negation excluding a NESTED member nulled ownership instead of
  falling back to the surviving outer member, certifying "a complete answer"
  over a suite that collects the file. Ownership now falls back through the
  previous-owner chain.

Round-3 Suggestions: caveat test names the shadowed shape; half-cap comment
arithmetic fixed (R3-11); the three stale "subset of workspaces the diff
touched" prose spots now say "plus the workspaces that depend on them"
(R3-12); test-plan merges scripts of parseable-but-nameless manifests and
reserves `unchecked` for genuinely unreadable ones (R3-14); the single-root
build-only test witnesses the executed commands (R3-16).

218/218 across the four touched test files; full src/commands/review suite
1751 passed, 1 unrelated real-git flake that passes standalone; typecheck,
eslint --max-warnings 0, and prettier clean.

* fix(cli): address review round 4 on scoped build/test honesty

Findings from the round-4 review of ec7b202:

- F1 (whole-call budget): the closure's per-command deadlines SUM, and on
  this repo a core diff's closure is 7 suites covering ~86% of test files —
  past the 600s ceiling the brief welds on, where the outer kill discards
  the report. The test loop now runs against a whole-call budget (--budget,
  default 2x --timeout = 600s): when the next command's deadline would
  cross it, the loop stops and names the suites that did not run, through
  testScope.caveat (or the note for single-root repos). A partial report is
  signal; a discarded one is the 71-timeouts failure this command exists
  to end.
- F2 (name collision): the root and packages/cli share the name
  @qwen-code/qwen-code on this very repo. The root now goes FIRST in the
  scope graph so members win the name map — last-write-wins would have
  resolved a dependent of the CLI package to the root and silently dropped
  the member's dependents from the closure.
- F3 (-ws alias): rootTestFansOut now also matches npm's -ws/--ws
  shorthands, while deliberately not matching -w/--workspace (singular).
- F4 (caveats compose): resolveTestScope disclosed at most two of five
  possible caveats — skipped short-circuited the outside-file facts, and
  any caveat suppressed the half-cap. All applicable caveats now compose,
  strongest first, because "nothing is silent" means composing the
  disclosures, not letting the first one hide the rest.

Smaller items from the same review:

- build/test commands shell-escape the workspace dir (PR-authored tree
  input; $() and backticks stay live inside double quotes on POSIX).
- The ok-note counts the root separately instead of reporting "of 23
  workspaces" on a 22-member repo.
- Dropped the pointless `const rootPackage = rootPkg` alias and the two
  no-op buildSet filters; un-exported workspaceDirCandidates (internal
  only); documented the previous-owner stack's contrived-ordering limit.

224/224 across the four touched test files; full src/commands/review suite
1757 passed (1 unrelated real-git flake that passes standalone); typecheck,
eslint --max-warnings 0, and prettier clean.

* fix(cli): address review round 5 on budget-stop honesty and root builds

Blocking and high findings from the round-5 review of 0c0e907:

- F1 (blocking): a budget stop left `testScope.workspaces` claiming suites
  ran that never did — the note said "Everything passed" two clauses from
  the caveat naming them not-run. The trim is now structural: `workspaces`
  is exactly the suites that ran, a new `notRun` field names the trimmed
  ones, and the zero-ran note branch says the budget was spent instead of
  "no workspace defines a test script".
- F2 (blocking): budget truncation was alphabetical, so the CHANGED
  workspace's own suite could be the one dropped (a `zebra` change ran
  `alpha`'s suite first). The test loop now runs affected workspaces first;
  the dependents are the widening, and the widening is what a budget trims.
- F3 (high): a root that devDepends on a workspace put `.` in the build
  set and ran a bare `npm run build` — the whole-monorepo build this module
  exists to stop. A fan-out root build (`--workspaces`/`-ws`/`--ws`) is now
  skipped like its test counterpart: an aggregator produces no artifacts of
  its own, and the scoped loop already builds the members it drives.
  `rootTestFansOut` generalized to `rootScriptFansOut(root, script)`.

Calibration and nits:

- F4: the budget default keeps 30s of headroom under the 600s tool ceiling
  (the clock outside starts before node does), floored at one per-command
  deadline so a tiny --timeout cannot go negative; option and docblock now
  say the budget covers the whole call, install included.
- F5: the half-cap caveat no longer says "of the suite" — it counts
  workspaces and now says exactly that.
- F6: shellArg's docblock scopes the claim to POSIX (cmd.exe is not a
  sealed surface); the "every return above ran zero test commands" comment
  no longer contradicts the nothing-to-run early return; failure notes
  carry the caveat too (the note is what the brief renders first); and the
  root docs/ tree is carved out of the influential set — a caveat that
  fires on most PRs teaches the reader to ignore caveats, while root-level
  prose (AGENTS.md, asserted on by load-rules.test.ts) stays influential.

229/229 across the four touched test files; full src/commands/review suite
1762 passed (1 unrelated real-git flake that passes standalone); typecheck,
eslint --max-warnings 0, and prettier clean.

* fix(cli): address review round 6 on budget admission and report honesty

Findings from the round-6 review of 17209f09:

- F1 (medium, efficacy): the budget guard reserved a full --timeout per
  suite, so at shipped defaults (300s deadline, 570s budget) at most one
  test command could ever run after install+build — the headline coverage
  was inert exactly where it mattered. The loop now ATTEMPTS every suite
  with whatever of the budget remains, up to its own deadline: a suite
  killed at the boundary is a timeout (already framed as infrastructure),
  and only suites never attempted are named notRun. A partial attempt is
  signal; a skipped suite was none.
- F2 (low-medium): results.buildSet kept '.' when the fan-out root build
  was skipped — the same class of defect round 5 fixed structurally for
  testScope.workspaces. The reported set is now filtered to what was
  actually (to be) built.
- F3 (low): rootScriptFansOut classified `--workspaces=false` — an
  explicit opt-OUT — as a fan-out, because `\b` matches before `=`. The
  flag must now stand alone: `--workspaces(?=\s|$)`.
- F4 (low): testScope.notRun had no consumer — the agent's brief now
  names it: suites the budget stopped before they ran are stated as not
  run, never folded into the coverage.

F5 (half-cap caveat meaning) and F6 (ok:true on a zero-suite run) are
answered without code change in the PR reply: the half-cap sentence tells
the agent not to oversell the scoping, and ok reflects failures, never
coverage — both fully disclosed.

229/229 across the four touched test files (the budget witness now uses
real wall clock); full src/commands/review suite 1988 passed (1 unrelated
real-git flake that passes standalone); typecheck, eslint
--max-warnings 0, and prettier clean.

* fix(cli): address review round 7 on budget coverage and caveat calibration

Findings from the round-7 review of 00556278:

- R7-1: the whole-call budget guarded only the test loop — a 300s install
  plus one 300s build already reached the 600s tool ceiling before any
  suite was attempted, discarding the report the budget exists to save.
  Install and every build now spend from the same budget (each command gets
  min(its deadline, what remains)). A build phase the budget cuts short is
  disclosed as `notBuilt`, filtered from the reported buildSet, and suites
  of unbuilt packages (plus their dependents, via the closure) are not run
  — running them would manufacture failures the diff did not cause.
- R7-2: a near-zero remaining slice manufactured a fake timeout (npm cannot
  boot in 200ms: exitCode null, ok flips false, the suite absent from
  notRun). A 15s attempt floor now routes those suites to notRun instead,
  and the timeout notes interpolate the deadline the command was actually
  given (CommandResult.deadlineMs), not the flag default.
- R7-3: the outside-file caveat fired on 38% of recent commits (measured)
  — .github/**, CHANGELOG.md, and editor/VCS dotfiles earn no caveat now
  (no workspace suite reads them, and the workflow tests that do live
  outside the npm workspaces either way). Same cry-wolf argument the docs/
  carve-out made, now with numbers.
- R7-4 (sizing): explicitly accepted — the PR body now states the cost
  honestly: on the most common closure (7 suites incl. core+cli, ~56% of
  workspace-touching commits) the 570s default realistically fits the
  affected suite plus a dependent or two, the rest disclosed in notRun.

Smaller items:

- A diff inside a negated member (!packages/desktop) no longer reads as a
  complete answer: the softest caveat says its own toolchain's suite was
  not run — without claiming the scope is incomplete.
- scriptFansOut is pure on the script text; readRootPackage returns
  RootPackage with scriptsText, ending the manifest re-reads.
- One named predicate (rootBuildSkipped) drives both the build-loop skip
  and the buildSet filter, fixing the singleRoot mismatch.
- TestScope.workspaces' docblock now says the order is scope order, not
  run order (test[] records execution order).
- New witnesses: the all-notRun note branch, notBuilt/notRun below the
  floor, the negated-member soft disclosure, the CI/changelog carve-out.

230/230 across the four touched test files; full src/commands/review suite
1989 passed (1 unrelated real-git flake that passes standalone); typecheck,
eslint --max-warnings 0, and prettier clean.

* fix(cli): address review round 8 — structural notBuilt for base-tree

Findings from the round-8 re-review of 0f1492f9:

- R8-1 (the one that mattered): a budget-truncated BUILD passed base-tree's
  availability gate — ok:true, toolchain npm, build[] non-empty — and got
  the success marker, so test-delta would rerun the PR's failing files
  against a base whose packages were never compiled; those reruns fail and
  land in `shared`, and "shared = pre-existing by measurement, never filed"
  waves a real regression through with the confidence of an A/B. notBuilt
  is now structural on BuildTestReport, and base-tree treats a non-empty
  one as unavailable — writing NO marker (truncation is a budget artifact,
  not a settled answer about the SHA), so a later shard may repay and
  succeed.
- R8-2 (carried over): the aggregate test-timeout note still quoted
  --timeout; it now interpolates the deadline the command was actually
  given, like the install and build-failure notes already did.

Nits:

- install gets the same 15s attempt floor as the build/test loops — a
  sub-second `npm ci` would only manufacture a fake timeout, so it is
  skipped and disclosed.
- notRun is sorted (both scope fields stable and comparable).
- rootSuffix now keys on the unified rootBuildSkipped predicate, and a
  single-root repo counts its one package as the one workspace again
  ("Built 1 of 1", not "0 of 1 (plus the root package)").
- The 中文说明 block in the PR body is rewritten to match the English
  (docs/CI carve-outs, all-phase budget, notRun/notBuilt, current counts).

246/246 across the five touched test files (incl. a base-tree witness:
truncated build → unavailable, neither marker written, next shard repays);
typecheck, eslint --max-warnings 0, and prettier clean.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-05 15:03:52 +00:00
Shaojin Wen
6b4a6295a2
feat(web-shell): run read-only info commands immediately mid-turn (#8496)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (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
Sync cua-driver to Aliyun OSS / Mirror cua-driver binaries to Aliyun OSS (push) Has been cancelled
* feat(web-shell): run read-only info commands immediately mid-turn

/stats, /about (/status) and /context were silently swallowed while a
turn was streaming, because their local user echo would act as a turn
boundary in applyTurnCollapse and split the active turn. Their output
is a status block, which is not a turn boundary and is not counted in
turn metrics, so only the echo needs to be skipped mid-turn.

Add appendLocalUserEchoIfIdle, which echoes when idle and skips the
echo while streaming without blocking the command, and switch these
three commands to it so their results render inline immediately even
during an active turn.

* fix(web-shell): keep streaming assistant block intact for mid-turn info commands

Address PR review. A status dispatch finalizes the active assistant
block by default, so running /stats, /about or /context mid-turn would
fragment the streaming answer around the status card and drop later
usage frames. Add an optional clearActiveText flag to the status event
and pass false from these three command dispatches, covered by
reducer-level tests.

Also collapse the echo gate into a single body (the new helper now
delegates to appendOrDeferLocalUserMessage), add App-level wiring tests
for the responding/idle behavior of /stats and /about, and surface
failed getStats via reportError instead of swallowing them.

* fix(web-shell): reset the echo user block when info output keeps streaming (#8496)

The clearActiveText: false opt-out skipped the whole clearActiveText call,
leaving the local command echo as the active user block indefinitely. A
peer client's prompt echo then merged into it, corrupting turn boundaries.
Keep the streaming assistant/thought block open on the opt-out path but
still drop the user pointer.

Also report /about load failures like /stats and /context already do, and
pin the new /context wiring plus the /stats failure path with tests.

* refactor(web-shell): centralize the mid-turn read-only status dispatch (#8496)

Address the round-3 review feedback on the mid-turn read-only commands:

- The read-only result dispatch (status block with clearActiveText:
  false plus the follow-resume) was copied verbatim at the /context,
  /stats, and /about sites, leaving the load-bearing flag enforced by
  convention at three places. Centralize it in one
  dispatchReadOnlyStatus callback next to echoLocalCommandIfIdle; the
  three .then bodies now call it with their serialized text.
- Pin the /about catch the way the sibling /stats catch is pinned:
  make collectSystemInfo throw once and assert the failure surfaces
  through console.error instead of becoming an unhandled rejection
  with zero user feedback.

* test: pin thought opt-out and serialized mid-turn status payloads (#8496)

* refactor(web-shell): consolidate read-only command echo into echoOrDeferLocalCommand (#8496)

* test(web-shell): cover /status routing and document the echo-suppression exception (#8496)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-05 14:05:08 +00:00
Shaojin Wen
477859bb3f
feat(channels): support local gh authentication (#8461)
* feat(channels): support local gh authentication

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

* test(channels): align registry catalog test and visuals with optional GitHub token (#8461)

* fix(channels): address review feedback for GitHub local gh auth (#8461)

Treat a blank replacement of an optional secret as a clear so an existing GitHub channel can no longer ship an empty or whitespace-only PAT to the daemon. Reuse the shared missing-field predicate in the editor's GitHub credential validation, wrap malformed baseUrl failures in an actionable channel error, and surface sanitized gh stderr in local authentication failures.

* fix(channels): address second-round review feedback for GitHub local gh auth (#8461)

Pin the whitespace-only token gate, the bounded gh stderr sanitization, and the required-secret blank-replacement guard with mutation-resistant tests. Log the authenticated account identity on channel connect so an out-of-band gh auth switch is visible to operators. Align test secret-source fixtures with the SDK union and complete the design doc's change footprint.

* fix(channels): address third-round review feedback for GitHub local gh auth (#8461)

* fix(channels): address fourth-round review feedback for GitHub local gh auth (#8461)

* fix(channels): address fifth-round review feedback for GitHub local gh auth (#8461)

* fix(channels): address sixth-round review feedback for GitHub local gh auth (#8461)

* fix(channels): address seventh-round review feedback for GitHub local gh auth (#8461)

* fix(channels): address eighth-round review feedback for GitHub local gh auth (#8461)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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@service.alibaba.com>
2026-08-05 14:05:00 +00:00
Shaojin Wen
a123d0030a
ci(review): prepare evidence-image tooling for GitHub-triggered reviews (#8454)
* ci(review): prepare evidence-image tooling for GitHub-triggered reviews

Reviews triggered on GitHub cannot attach images today for three
reasons; this wires the two that live in the workflow:

- Install tmux and freeze (pinned, checksum-verified) before the
  review runs. Both are optional by contract — the evidence ladder
  degrades honestly without them (png -> ans-only -> refused, recorded
  in the capture manifest) — so the step never fails the review; it
  only decides which rung the runner can reach. tmux mirrors the
  tolerant install qwen-autofix.yml already uses; freeze falls back to
  ~/.local/bin when passwordless sudo is absent.

- Pass QWEN_REVIEW_ASSETS_REPO from a repository variable to the
  review step. Publishing stays OPT-IN by design: with the variable
  unset the env is empty and publish-assets refuses (parseAssetsRepo
  trims and rejects empty), so nothing changes until a maintainer sets
  the variable. When set, evidence images land on commit-pinned
  pr-assets/<pr>-review branches — already covered by the visuals
  cleanup workflow — pushed with the same CI_BOT_PAT the step uses.

The third reason is release lag: the capture producer (capture-tui,
#8388) has to merge and ship in a release before rendering claims can
generate images on CI at all. This change is inert until then.

* fix(ci): capture-tools step review fixes — enforced tolerance, version pin, cached fallback

R1-1: the never-fails contract is now enforced twice — continue-on-error
at the YAML level (the belt) and set +e with a trailing exit 0 inside
(the suspenders); under the runner's default bash -e several statements
(mktemp, install, sudo install with an empty path) could previously
abort the step and fail the review the comment promised never to fail.

R1-6: probe the VERSION, not just the binary — on a persistent
self-hosted runner an installed freeze made any FREEZE_VERSION/SHA
bump a silent no-op; the pin now forces a refresh when the cached
binary does not match.

Cached-fallback fix: put ~/.local/bin on PATH (and GITHUB_PATH) before
the probe — a sudo-less runner otherwise re-downloads the tarball on
every review run forever.

R1-3: the step comment says capture-tui is UPCOMING (#8388, not in the
released CLI) and names qwen review drive as today's tmux consumer, so
the step cannot be mistaken for stale dead weight and deleted from
under the follow-up.

R1-4: the retention comment scopes the cleanup-workflow claim to the
same-repository designation; a fork or scratch destination manages its
own retention (docs updated to match, plus a note documenting the
repository VARIABLE a maintainer sets to enable publishing).

R1-5: the step's real bash now runs in the workflow behavioural
harness under bash -e with stubbed sudo/apt/curl/sha256sum/tar/uname:
worst-runner and checksum-reject scenarios exit 0 installing nothing,
the no-sudo happy path pins the ~/.local/bin + GITHUB_PATH pairing,
and the version-pin probe is pinned from both sides (wrong version
re-downloads, matching version skips). Real freeze/sudo on a
developer machine are shadowed so the tests are deterministic and can
never install to /usr/local/bin.

Nit: both sudo guards now check sudo -n true.

* fix(ci): capture-tools step review fixes — step-owned tool dir, anchored probe, honest failures

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

* fix(ci): capture-tools test harness — shadow tmux, don't blank its PATH dir

The harness dropped every host PATH directory that ships a tmux so the
step's apt branch would depend on the scenario, not on the machine
hosting the suite. On GitHub-hosted ubuntu runners tmux lives in
/usr/bin, so the filter blanked /usr/bin wholesale — bash, grep, mkdir,
and tar included — and execFileSync('bash') died of ENOENT: all seven
capture-tools tests failed in the Test (ubuntu-latest Node 22.x) job
while passing on tmux-less dev machines.

Replace the directory-level drop with an entry-level shadow: each
tmux-bearing directory is mirrored (symlinks) into a scratch dir minus
the tmux entry, in place, preserving PATH order and the empty-entry
stripping the old filter did. Hosts without tmux take the map through
unchanged, and Windows (no tmux in its PATH, no symlink branch) keeps
its current behavior exactly.

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

* fix(ci): capture-tools test review fixes — faithful stubs, env shape pins, missing-branch scenarios

Round-3 review findings: the harness executed several of the step's
branches but asserted nothing about them, so probe-verified mutants
(dropped tmux guard, deleted warning/degradation messages, malformed or
missing FREEZE_VERSION/FREEZE_SHA256, wrong hash variable, dropped URL
`v` prefix or curl `-L`, severed tarball paths, broken regex boundary,
leaked mktemp dir) all shipped green.

- Make the curl/sha256sum/tar stubs model their real contracts: exact
  pinned URL, pinned checksum over a file curl actually wrote, existing
  -xzf operand
- Pin FREEZE_VERSION/FREEZE_SHA256 shape in captureToolsSource
- Pin the full curl flag set and the three-site tarball path agreement
- Assert the stale-renderer warning (fires on degraded re-download,
  silent on the happy path) and the tmux-unavailable message
- Pin TMPDIR and assert the mktemp cleanup leaves it empty
- Add the two missing scenarios: tmux-present skips apt, cached version
  extending the pin with a leading digit re-downloads

Verified by 13 mutation probes: every named mutant now turns the suite
red (13/13 killed), baseline 34/34 green.

* fix(ci): capture-tools step review fixes — hash-verified cache, per-run PATH promotion

* fix(ci): capture-tools step review fixes — verified-bytes-only installs, step timeout

Review findings on the capture-tools step:

- Drop the PATH-trust branch: a freeze already on PATH was accepted on
  its own --version and executed to probe it — exactly the self-report
  the FREEZE_BIN_SHA256 comment declares attacker-controllable, from
  dirs writable between jobs on both runner classes. The checksummed
  download always runs now; the cache makes it free after the first
  run.
- Guard $tools_bin in the download branch: with mktemp failing, the
  unguarded install resolved to /freeze — harmless unprivileged, but a
  root-in-container self-hosted runner writes it and reports success
  with nothing on PATH.
- Copy-then-verify the cache: install into the fresh per-run dir FIRST,
  verify THOSE bytes, delete both copies on mismatch — the verified
  bytes are the bytes later steps execute, closing the check-then-copy
  race for free. This makes the separate pre-verify block redundant;
  it is deleted.
- Add timeout-minutes: 5 — continue-on-error bounds failure, not
  duration, and a stalled `sudo apt-get update` mirror had no other
  bound under the 300-minute job cap.
- Report block: say the resolved freeze is likely broken when its
  --version produces nothing, instead of echoing a blank line and
  calling it stale; the mismatch wording is direction-neutral now.

Tests: replace the PATH-trust scenario with a planted-PATH one (marker
outside the scenario dir proves the plant never executes), add the
mktemp-failure scenario (the install stub succeeds like root would, so
the unguarded mutant is caught) and the promoted-dir 0700 assertion;
re-anchor the two digit-boundary tests on the report's warning. 41/41
green; both fix mutants verified killed.

* fix(ci): capture-tools review fixes — stale-dir cleanup, pinned guards

Address round-5 review:
- R5-1 (Critical): the per-run qwen-review-tools.* dir under RUNNER_TEMP
  was never removed; RUNNER_TEMP survives across jobs on the shared pool,
  so every review run accumulated one dir + one Go binary, unbounded.
  'Clean stale agent state' now removes stale dirs before the install
  step creates the current run's dir, matching the qwen-triage.yml
  convention. The harness comment claiming the dirs were runner-cleaned
  is corrected.
- R5-6: the cache re-verification rejection branch now logs why it
  deletes the cached binary instead of degrading silently.
- R5-7: bump-checklist note beside the freeze pins — the harness stubs
  key on the same env values, so a transposed hash pair must be caught
  against the real release artifacts at bump time.
- R5-2/R5-3/R5-4/R5-5: four unpinned step properties now pinned (the
  if: guard, the sudo -n probe flag, install-after-context ordering, and
  the cache branch's tools_bin guard via a new mktemp-fails scenario);
  six mutation probes confirm each pin kills its mutant.

* fix(ci): capture-tools review fixes — curl budget, swept scratch dir, wiring pins

* fix(ci): capture-tools review fixes — harness mutation pins, pin-pair self-check

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

* fix(ci): capture-tools review fixes — shadow-farm cleanup, backoff budget term

* fix(ci): capture-tools review fixes — report probes only installed freeze, age-gated sweep

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-05 13:16:56 +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
顾盼
93cd019088
fix(release): reuse configured Apple signing secrets (#8574) 2026-08-05 09:20:17 +00:00
顾盼
a5c637b749
feat(web-shell): add native Live Voice (#7859)
* feat(web-shell): add native Live Voice

* fix(web-shell): address review feedback for Live Voice PR (#7859)

- Quote all strings in electron-builder.yml to fix yamllint CI failure
- Gate discovery publish on liveVoiceEnabledAtBoot to avoid writing
  bearer token to disk when Live Voice is disabled (M1)
- Add child identity guard to CommandMonitor stdout/stderr handlers
  to prevent stale helper output from corrupting the new buffer (M4)
- Add exponential backoff to sent-completion delivery retry (M3)
- Skip broadcastState when setCallState/setTranscript value is
  unchanged to reduce per-audio-delta overhead (H1)
- Document sent-mode completion notification in module docstring (H2)
- Remove dead protocol/nonce aliases from readDiscoveryFile
- Fix single instance lock fall-through with process.exit(0)

* fix(cli): register realtime_voice in docs contract and env guard (#7859)

* fix(web-shell): address review feedback for Live Voice PR (#7859)

* fix(cli): discard orphaned isolated dir when parent restore fails (#7859)

* fix(web-shell): address review feedback for Live Voice PR (#7859)

* fix(serve): harden live turn recovery

* fix(desktop): restore Live Host native build

* fix(live): align native host and session isolation

* fix(acp): preserve live worker continuation lineage

* fix(live): classify provider close reasons

* fix(serve): discard unused recovered conversation dirs

* fix(live): isolate authorized realtime responses

* fix(live): preserve realtime response authority

* feat(web-shell): complete Live Voice onboarding

* fix(live): persist realtime-owned dialogue

* fix(live): preserve final speech while stopping

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit 7110bec6b034c702bca6e28e35b93c7f70e729cd.

* Revert "fix(cli): discard orphaned isolated dir when parent restore fails (#7859)"

This reverts commit 85165f1b2ddfaa311b8be91acdd76a6f388f6204.

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit 9199fa633e102bb8f24e4b216d322be4323eb3fc.

* Revert "fix(cli): register realtime_voice in docs contract and env guard (#7859)"

This reverts commit 6b6b1718352ef01a98a73976b5c7c4433fd14c35.

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit e083779105199d26de3afd8ad00719a08efe3099.

* revert(live): remove remaining takeover behavior

* revert(live): restore pre-rollback implementation

* test(cli): align Live diagnostics env guard

* test(release): cover Live Host publication

* fix(ci): re-sign Live Host package before verification

* fix(serve): scope sent completion notifications to Live

* fix(web-shell): preserve live setup errors

* fix(live): align realtime backend speech lifecycle

* ci(live): publish Live Host independently

* test(cli): mock Live speech bridge handler

* test(release): align Live Host workflow contract

* fix(live): address release and lifecycle review findings

* fix(live): release completed call tracking

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-05 08:33:22 +00:00
顾盼
67d128715e
chore(cua-driver): sync upstream v0.17.0 (#8564) 2026-08-05 07:00:36 +00:00
Shaojin Wen
6d4d9b5238
perf(review): retire dry chunks and pipeline verification in the reverse audit (#8498)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (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
* feat(review): retire dry chunks from reverse-audit rounds and pipeline verification

Rebuild of the retirement/pipelining feature as one commit on top of the
merged reverse-audit budget gate (#8468).

- retirement.ts: per-chunk scheduler over the CLI's own prompt records
  and the harness transcripts — a chunk whose two most recent audits are
  substantive dry receipts is cold-checked on the even rounds instead of
  audited on every one, and a cold check that yields returns it to
  every-round auditing. The certifying match is counted records per
  transcript (one launch matching several records certifies none), dry
  receipts are read structurally with the zh forms beside the English
  ones, and a filed finding requires the File+Severity pair so an echoed
  quotation cannot pin a chunk hot. Everything fails toward auditing.

- agent-prompt --all-chunks: requireAuditableChunks, then the schedule
  (round >= 3, fail-open to all-due on any error), then CONVERGED exit 5
  (nothing built, no stamp, no marker), then the budget gate (exit 4 +
  marker), then the build. The admission stamp keeps the #8468 ordering
  and lands only after the build succeeds: a cold-check-only round that
  builds still stamps, a converged round never does, and a build that
  throws leaves no stamp.

- agent-prompt --chunk: a round holding an admission stamp is repaired
  without gates or scheduling; an unadmitted round answers to the same
  sequence as --all-chunks (convergence, then budget), and its first
  chunk build is the round's admission — stamped after the build.

- prompt-record: optional sinceMs fence on readRecordedPrompts (history
  readers only; coverage's obligation reads stay unfenced), plus the
  flattenPrompt/deliveredVerbatim split so the scheduler flattens each
  launch once instead of once per (record, transcript) pair.

- deadline: doc-comments rewritten for the pipelined cadence — the
  admission-to-admission measure no longer contains a verification pass,
  so the tail reserve is the terminal round's only cover (replacing the
  'deliberate margin' overlap rationale), and the workflow's reserve cap
  is cross-referenced. runEpochMs fencing, the bilingual budget-stop
  marker and the stamp semantics are unchanged from #8468.

- SKILL Step 5: builder-owned 3B scheduling, the CONVERGED exit-5
  termination rule, verification launched alongside the next round's
  auditors, and the cumulative reported list with '— [unverified]'
  tagging (added at the admitting merge, cleared or removed after the
  verdict; anything still tagged is excluded from Step 6).

Superseded pieces of the parallel branch were dropped in favour of the
#8468 form now on main: the planMtimeMs-equality fence (runEpochMs
stays), the budget-scaled round-1 estimate, stamping inside the
admission helper, and the branch's variants of the budget-gate tests.

* test(review): split the tool-call guards in the retirement classifier tests

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

* fix(review): harden the retirement classifier and pin its gate orderings (#8498)

Review-round fixes for the per-chunk retirement feature:

- retirement.ts: admit the full-width colon (U+FF1A) in zh dry receipts,
  which the old [::] class silently refused; refuse the brief's own
  example receipt and prose artifacts (a stray backtick, the conjunction
  "and/or") in the substance check; require the transcript's diff reads
  to overlap the chunk's baked territory before a dry receipt counts;
  stop counting a quoted cumulative-list entry — a File+Severity block
  whose file line appears verbatim in the agent's own launch prompt — as
  a filed finding. Every change fails toward auditing.

- agent-briefs.ts: export the reverse-audit example receipt
  (REVERSE_AUDIT_EXAMPLE_RECEIPT) and interpolate it in the brief, so
  the brief and the classifier's parrot refusal cannot drift.

- Tests: rewrite the transcript-fence test, which a future-dated plan
  made vacuous, and add probe-verified pins for every fix plus the
  previously unpinned guards — converged-before-budget under deadline on
  the --chunk path, the --chunk transcripts-unavailable degrade, the
  stamp-keyed repair exemption (records without a stamp stay refused),
  budget-stop marker absence on the admission side, uncertified
  cold-check recovery, the history-less chunk guard, and the same-round
  multi-record merge in both digest orders.

- SKILL.md: schedule the Step 5 findings merge unconditionally — verdicts
  land on dry rounds too — and cap a would-be Approve at COMMENT when a
  reverse-audit entry's verifier never ruled on it.

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

* fix(review): bind the retirement territory to the diff read and reprice the budget gate (#8498)

* fix(review): machine-check the unverified-tag backstop and reprice the tail reserve (#8498)

---------

Co-authored-by: verify <verify@local>
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-08-05 06:32:18 +00:00
Shaojin Wen
da37110e60
perf(autofix): build the review CLI bundle once per scan and fan it out to legs (#8548)
* perf(autofix): build the review CLI bundle once per scan and fan it out to legs

Each review-address leg repeated the same trusted-base build: measured
3.5-5 minutes of npm ci + build + bundle per leg (~25 runner-minutes on
one 6-leg scan) before the agent could start. A build-cli job now
compiles the bundle once per scan, uploads the repo-root dist/ as an
artifact, and the legs download it; their checkout is pinned to the
compiled SHA so a mid-run base push can never pair a leg's bundle with
different sources. The legs keep npm ci (the agent and the verify gate
still need node_modules against the PR branch), and the issue phase is
untouched — it runs only when no review targets exist, so gating the
build on do_issue too would rebuild on every quiet scheduled tick.

* fix(autofix): validate fan-out bundle SHA and pin shared CLI recipe contracts (#8548)

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-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 05:16:49 +00:00
Shaojin Wen
8b0e8b8192
fix: add onCompromised handlers to proper-lockfile calls to prevent daemon crash (#8442)
* fix: add onCompromised handlers to proper-lockfile calls to prevent daemon crash

When a lock.lock directory is deleted while held (e.g. by another
process cleaning stale locks), proper-lockfile's updateLock timer
gets ENOENT on stat and calls the default onCompromised handler,
which throws and crashes the process.

Add onCompromised handlers that log instead of throw, consistent
with existing handlers in tasks.ts and mailbox.ts.

* fix: guard lock release after compromise and cover trusted folders (#8442)

* fix: guard mailbox/task lock release and cover compromise handlers with tests (#8442)

* test(core): add lock-compromise regression tests for mailbox and task guards (#8442)

* test: share lock-compromise simulation via test-utils helpers (#8442)

---------

Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-05 03:05:28 +00:00
jinye
3084326243
fix(webui): recover complete turns after live journal truncation (#8414)
* fix(webui): recover live journal turns

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

* fix(webui): address PR review feedback (#8414)

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

* fix(webui): refresh repair pagination anchor (#8414)

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

* fix(webui): restore evicted repair side effects

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 02:50:58 +00:00
易良
e34780e24d
fix(ci): clean review worktrees after cancellation (#8474)
* fix(ci): clean review worktrees after cancellation

* fix(ci): remove orphaned review worktree directories

* fix(tests): sync qwen-resolve-workflow expectations with externalized review timeouts (#8474)

* fix(ci): pin review worktree cleanup patterns to paths.ts (#8474)

* fix(ci): harden review cleanup sweeps and cover integration_cli (#8474)

* fix(ci): extend review cleanup sweep to web_shell_e2e_smoke (#8474)

* fix(ci): harden review cleanup git calls

* fix(ci): tighten review cleanup comments and test guards (#8474)

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

* test(ci): pin review cleanup recipe copies byte-identical (#8474)

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

* fix(ci): guard review worktree removal and pin cleanup invariants (#8474)

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-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 02:39:36 +00:00
Shaojin Wen
2601d815dd
test(ci): align resolve-workflow timeout expectations with externalized variables (#8460 follow-up) (#8485)
* test(ci): align resolve-workflow timeout expectations with externalized variables

#8460 moved the review timeout numbers into repository variables
(QWEN_REVIEW_JOB_TIMEOUT_MINUTES / QWEN_REVIEW_MAX_TIMEOUT_MINUTES)
but left qwen-resolve-workflow.test.js expecting the old hardcoded
300/240 values — main's Test lane has been red since. The five
expectations now pin the variable-driven forms the workflow actually
carries.

* test(ci): pin resolve-workflow timeout enforcement, tiering, and fallback machinery

* test(ci): pin the timeout-advice branch CONDITION, not just both bodies

With both branch bodies asserted as substrings of the same step, any
comparison flip (-ge/-gt/-le) keeps both strings present and ships the
wrong recovery advice on every timeout — a below-max run told it
already used the maximum, an at-cap run told to retry with a timeout
that cannot work. Raised in #8388's review round (R4-21).

* test(ci): anchor resolve-workflow arm slices at their if starts

Address review feedback on #8485: search for each sliced arm's else from
the arm's own if start so an unrelated earlier if/else in the step cannot
invert the slice (or vacuously satisfy .not.toContain on an empty slice).
Also slice the small-PR tier arm instead of comparing first-occurrence
indexes, unifying the technique and proving the assignments sit in
opposite arms.

* test(ci): keep the max-timeout text out of the below-max arm slice

* test(ci): pin fallback arm bodies, comment wiring, and tiering order

* review: line-anchor the at-max arm's closing fi

A bare indexOf('fi') stops at the first word CONTAINING the letters —
'specified', 'notification' — so a harmless wording change inside the
at-max arm would silently truncate the slice and hand the arm's
not-toContain a vacuous pass. The end anchor is now the line-anchored
/\n\s*fi\b/, with its own found-assertion.

---------

Co-authored-by: Qwen Autofix <autofix@qwen-code.dev>
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: verify <verify@local>
2026-08-05 02:33:04 +00:00
Shaojin Wen
1a2bb10298
fix(review): admit evidence images by content, not by name — magic-byte sniffing (#8459)
* fix(review): admit evidence images by content, not by name — magic-byte sniffing

The publish-assets allowlist was extension-based, and an extension is
a claim anyone can make: combined with a prompt-injected review run,
whatever could name a file evidence.png could host up to the size cap
of arbitrary bytes at a github.com URL through the evidence push
(raised in #8454's review as an enablement consideration).

sniffImageFormat reads the four admitted signatures (PNG, JPEG,
GIF87a/89a, RIFF+WEBP — RIFF alone is not enough, AVI and WAV share
the container prefix) and validateAssetContent rules the content
against the format the extension claims, fail-closed: an unrecognized
signature refuses even when the extension is allowed. publish-assets
applies the ruling to every file's first bytes before anything is
uploaded — all-or-nothing, same refusal contract as the other gates.

Pinned: the four signatures, truncated/empty headers, the RIFF/AVI
near-miss, extension-format mismatch, fail-closed unknown extensions,
and end-to-end: a shell script named evidence.png refuses with exit 3
and nothing pushed.

* fix(ci): re-pin review timeout tests to vars externalization (#8459)

* fix(review): single-source the asset allowlist and pin sniff depth (#8459)

Address round-1 review suggestions:

- ASSET_EXTENSIONS now derives from EXTENSION_FORMAT, so admitting a
  format is a one-place change the batch gate and the content gate
  cannot drift on; the duplicated extension extraction moves into one
  claimedExtension helper shared by both gates.
- Pin what the mutation probes showed unpinned: the GIF87a branch
  (sniff + admission), uppercase extensions at the content gate, the
  refusal message direction, the full depth of every signature
  (near-miss negatives), and the publish-time 16-byte slice end to end
  (a WEBP publishes through runPublishAssets; verified the test fails
  when the slice shrinks to 8).

* fix(review): pin every sniff check and guard the allowlist lookup (#8459)

* fix(review): pin every sniff byte and the two-gate format coupling (#8459)

* fix(review): align the two-gates comment with the pin that enforces it (#8459)

* fix(review): sharpen evidence-gate diagnostics and pins (#8459)

- Content refusals now name the JSON-quoted full path, so two same-named
  files from different directories are tellable apart (the sibling
  read-error refusal already spent the path).
- Export ASSET_HEADER_BYTES from the lib that owns the sniff depth; the
  publish call site uses it and the two-gates pin slices canonical headers
  to it, so a future longer signature fails the pin, not real publishes.
- One shared refusal builder keeps the allowlist message identical in
  validateAssetFile and validateAssetContent.
- Narrow the sniffImageFormat threat model to what magic bytes buy: binds
  the claimed type to the leading bytes, does not stop prefixed payloads.
- One-byte-off matrix becomes a labeled it.each table (failures name the
  exact corrupted byte); imports re-alphabetized.

* test(review): pin the shared extension refusal across both asset gates (#8459)

* test(review): pin lastIndexOf extension parsing for multi-dot asset names (#8459)

* fix(review): admit WEBP by its fourcc and name refused files once (#8459)

---------

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@service.alibaba.com>
2026-08-05 02:23:25 +00:00
Shaojin Wen
4f79036a22
feat(review): a cost ledger from the records already on disk (#8471)
* feat(review): a cost ledger from the records already on disk

"0.21.3 was fine, 0.21.4 got slow" was settled only by replaying a whole
review under a telemetry exporter and hand-aggregating the output — hours
of forensics to find a repair round that had silently doubled a run
(measured: a +93/-48 PR at high effort cost 523 model calls and 37.8M
input tokens, 9.7M of them redelivering prompts the agents had already
acted on). The usage data was on disk the whole time: every chat and
subagent transcript event carries usageMetadata.

qwen review cost-ledger --plan <plan> aggregates those records — the same
files check-coverage trusts for delivery, found via the same
environment-exported location, floored at the plan's mtime so a review
started an hour into a session does not bill that hour — into per-stream
totals: the main loop and each agent, with input / cached / output /
thinking counts and wall time. Step 8 pastes the printed block into the
saved report, so the next slowness question is a diff of two archives
instead of an excavation.

Informational by construction: an incomputable ledger prints why and
exits 0. Validated against the measured run above — 521 calls, 37.7M
input (93% cached), 849k output, 107 min — matching the telemetry-side
aggregation, minus the two side-query calls that are not the review's.

* test: register cost-ledger in the subcommand registry and its demand message

* fix(review): honest cost-ledger output and a safe archive write (#8471)

Address the review of the cost ledger: report output tokens once
(thinking is a subset of candidates, not a sibling), keep the --out
write inside the exit-0 contract and mkdir its parent, name a missing
plan as the plan, read each transcript once, compare timestamps as
instants, fold relaunched agents into marked rows, and archive the
full ledger next to the Step 8 report.

* fix(review): cost ledger — honest failures, validated plan, shared records (#8471)

Address the second review round: a missing or faulted chat transcript
now says "cost-ledger unavailable" instead of rendering agents-only
totals as the whole cost (the plan proves the main loop ran), and a
subagent dir that fails listing with anything but ENOENT does the
same. The --plan file is validated as a plan report before its mtime
alone sets the billing window. Output derives from totalTokenCount −
promptTokenCount when present, correct under both usage conventions.
Chunk agents label "chunk N" via the shared CHUNK_RE instead of the
malformed "agent chunk N of M"; the transcript listing is one helper
shared with the coverage gate; glued JSONL records are recovered via
parseLineTolerant; totals reuse the rows' accumulator; folded (×N)
rows rank by combined total; stale agent files are skipped by mtime
without being opened. Rendered block gains pluralization, "agent
runs: N", and a B tier; SKILL.md states the ledger's bounded window.

* fix(review): close the cost-ledger audit — refusals, labels, pinned math

Address the remaining review threads on the cost ledger:

- Refuse agents-only totals when the chat file exists but holds no
  above-floor records: a degraded recorder leaves the file present and
  empty while agents run — the same infrastructure fact as an
  unreadable transcript, and exactly the output the missing-file
  refusal exists to prevent.
- Read the agent label from the first user record, not a raw 64KB head
  slice: a fork's agent_bootstrap record precedes the launch prompt,
  quotes other agents' identity lines, and can outgrow any fixed
  window.
- Distinguish parallel invariant agents by their owned file, so
  per-file runs stop folding into a phantom (xN) relaunch row.
- Coerce negative provider counts to zero: the agent path records
  usage uncoerced, and summed negatives rendered >100% cached shares.
- Accept degraded diff-less Step 1 reports: validate diffLines +
  chunks, the pair every plan report carries, instead of
  check-coverage's stricter contract that refused them.
- Pin every branch the second round proved unobservable: the exit
  code on all handler paths, total - prompt under both usage
  conventions, per-agent fault tolerance, the wall-minutes
  conversion, array-shaped usage, the mtime pre-filter and the
  event-level floor, human() rounding, per-condition plan validation,
  sort order against a lexical readdir, the zero-event skip, the
  --out per-stream archive contract, error messages naming their
  paths, truncation membership and folded-run counting, and the
  assistant-type filter.

Every new assertion was mutation-probed: each mutant the review named
now turns the suite red.

* review: pipeline stages keep their own ledger rows

The (×N) fold keyed on the label alone, and three legitimate multi-launch
shapes shared one: a reverse-audit chunk auditor is launched with the same
'chunk N of M' identity as the Step 3B territory finder (five audit rounds
folded into the finder's row — one agent where six pipeline stages ran),
and repeat rounds of the findings roles carry their round OUTSIDE the
backticks (every round folded as a phantom relaunch). labelOf now reads
the stage from the audit brief's record key in the launch (audit chunk N
(round K)) and the round from the identity LINE — never the whole launch,
whose folded findings can quote a budget disclosure's own '(round N)' —
so rounds are rows and only true relaunches and same-round verify shards
fold. The (×N) comment now says what the marker means: N runs under one
label.

* fix(cli): annotate cost-ledger test helper to restore strict build (#8471)

* fix(cli): anchor cost-ledger labels and harden broken-usage defenses (#8471)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-05 02:18:06 +00:00
jinye
32e2741577
perf(core): clear tool results to a low watermark to preserve prompt cache (#8464)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (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
* perf(core): clear tool results to a low watermark to preserve prompt cache

Size-triggered microcompaction now clears oldest compactable tool results down to half the threshold instead of stopping just below it, so the conversation prefix stays stable between clearings and provider prompt caches keep matching. The recent-result budget now protects committed results only; pending results no longer consume protection slots but stay counted, uncleared, and live for file-read-cache resolution. Adds the watermark to cleanup metadata and the debug log.

Fixes #8463

* fix(core): harden size-cleanup protection against zero-char and pending refs

Review follow-up for the low-watermark change: keepRecent now selects from committed results that are actually clearable (positive, successful, uncleared output), so trailing errors, prior placeholders, and empty outputs no longer absorb protection slots. Pending refs are dropped from the keep set entirely — a pending read may be a cache-hit placeholder rather than file bytes, so it must not suppress eviction reporting; over-disarming only costs a redundant re-read. Adds regression tests for both plus the protected-saturation consecutive-trigger corner.

* qwen: address PR review feedback (#8464)

Pin the (soft-exceeded) log marker with the one-line assertion suggested by the sandboxed verification report (finding S-1): the all-protected overage test now asserts 'target 250000 (soft-exceeded)', killing the surviving mutant M4.

* qwen: address PR review feedback (#8464)

* qwen: address PR review feedback (#8464)

* qwen: address PR review feedback (#8464)

Two P1 context-integrity fixes from review: (1) media-only tool results (image/PDF reads with empty text output and bytes on functionResponse.parts) stay in the idle-path keepRecent candidates instead of being dropped by the zero-char filter; (2) only write_file results vouch for file residency in kept-path accounting — edit calls carry just old/new snippets while still setting the cache's sticky full-read flags, so a kept edit can no longer suppress eviction reporting after the full read is blanked. Regression tests for both.

* qwen: address PR review feedback (#8464)

Pin the absence of the (soft-exceeded) marker at the exact watermark boundary: clearing that lands the virtual total exactly on the watermark must not be flagged. Kills the >= and always-true mutants of the marker condition that previously survived the suite.

* qwen: address PR review feedback (#8464)

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-04 19:00:11 +00:00
jinye
2bd5c94111
fix(serve): detect lineEnding across the file, not the returned slice (#8383)
* fix(serve): detect lineEnding across the file, not the returned slice

`readText` reported `meta.lineEnding` from the slice it was about to return.
A slice holding a single CRLF line arrives as text ending in '\r' — the '\n'
was consumed as that line's terminator — so detecting on it answers 'lf'. Page
one of a cursor sequence then disagreed with page two about the same file, and
a client that trusts the first page would rewrite CRLF content as LF.

The truncation branch re-detected on the truncated slice for the same reason
and had the same flaw.

Detect on the whole decoded file once, which is what the field is describing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* test(serve): guard byte-truncated reads against slice-based lineEnding re-detection

* fix(core): report crlf on cursor pages resuming after a CRLF terminator (#8383)

* fix(core): count a skipped CRLF terminator on byte-truncated pages (#8383)

When a window's first line exceeds both the read-chunk size and
maxOutputBytes, the byte cut fires before the line's terminator is decoded,
and the re-snap then walks over that terminator without reading it. The next
page seeds from the pair and reports 'crlf' while the cut page reported 'lf'
— adjacent pages of one file disagreeing, the exact symptom this PR removes.
Consume the same two-byte evidence after the re-snap so the pages agree.

Also qualify the design-doc agreement guarantee to files with uniform line
endings (mixed-ending files can still flip between pages), and pin the
seed's load-bearing placement with tests: it must run on the snapped offset,
and the minimum probe offset (startOffset == 2) is now covered.

* docs: correct the lineEnding spec for byte-cursor pages (#8383)

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

* docs: qualify the mixed-EOL verification bullet for byte-cursor pages (#8383)

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

* docs: name the uniform-file line-window-vs-cursor lineEnding split (#8383)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-04 16:26:54 +00:00
易良
3ad4fbdb7d
fix(cli): preserve Qwen Review startup version in footers (#8431)
* fix(cli): preserve review startup version in footers

* fix(cli): keep review startup version dynamic in bundle

* fix(cli): reset review version after managed update

* test(cli): use indexed env access

* fix(cli): harden review footer strip and version stamping (#8431)

* fix(tests): sync qwen-resolve-workflow expectations with externalized review timeouts (#8431)

The timeout externalization in #8460 replaced the hardcoded 300/240
values in qwen-code-pr-review.yml with the QWEN_REVIEW_JOB_TIMEOUT_MINUTES
and QWEN_REVIEW_MAX_TIMEOUT_MINUTES repository variables but left
scripts/tests/qwen-resolve-workflow.test.js asserting the old literals,
so the full-profile Test job fails on any branch carrying that change.
Update the three affected assertions to pin the externalized shape.

* fix(cli): harden the review footer strip per review feedback (#8431)

The strip regex kept a 2^(N-1) partition ambiguity for same-line footer
runs (measured 5.3 s at n=20) and missed footers truncated before their
closing `_`; forged footers also survived on the body channel through
`bodyCriticals`, and the values interpolated into the footer were not
shape-validated. Guard the repeated group so an iteration cannot span
another footer's start, make the final `_` optional, strip body
Criticals per entry, refuse footer-forging model ids and non-version
stamps, refuse non-object comment entries, pin the CLI-glue test suite
against an ambient startup stamp, and cross-assert the LGTM filter
regex against the footer builder.

* fix(cli): strip review footers before ledger carryover

* fix(cli): align ledger footer regression expectation

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-04 14:58:56 +00:00
Dragon
bf9b47e5e3
fix(core): reuse prompt cache for multimodal compression (#8419)
* fix(core): reuse prompt cache for multimodal compression

* test(core): remove inert media modality fixture

* fix(core): guard compression cache-sharing window

* fix(core): harden compression cache-sharing gate

* fix(core): tighten compression cache preflight

* test(core): pin lazy compression slimming
2026-08-04 14:48:05 +00:00
易良
21ad3b464a
fix(desktop): codesign ripgrep and node binaries before tauri build (#8518)
* fix(desktop): codesign ripgrep and node binaries before tauri build

macOS notarization rejects the app bundle because Tauri only signs the
main binary, not the embedded ripgrep and Node.js runtime binaries under
Contents/Resources/runtime/qwen-code/. Add a pre-build codesign step
that signs all native macOS executables in the bundled runtime with the
Developer ID identity, hardened runtime, and the existing entitlements.

* fix(desktop): allow Windows build without signing certificate

The Tauri release workflow threw when WINDOWS_CERTIFICATE was missing,
blocking the entire release (including macOS). Mirror the old Electron
workflow behavior: warn and continue unsigned when no cert is configured.

Also add fallback to legacy WIN_CSC_LINK/WIN_CSC_KEY_PASSWORD secrets
so existing Electron-era credentials still work if present.

* fix(desktop): allow unsigned Windows artifacts in verify step

The 'Verify Windows signature' step threw on any non-Valid status,
including NotSigned. With no Windows code signing certificate
configured, this blocked the Windows build job, which in turn
blocked the publish job (needs: [prepare, build]).

Allow NotSigned with a warning instead of throwing, matching the
fallback behavior of the 'Import Windows certificate' step. A
genuinely invalid signature (HashMismatch, etc.) still throws.

* fix(desktop): narrow find scope and deduplicate pfx import

- Scope ripgrep codesign find to *-darwin/* paths so Linux ELF
  binaries (built in the same matrix) aren't targeted.
- Unify the two pfx-import branches into a single code path to
  eliminate duplicated write/import/configure logic.

* fix(desktop): guard optional Windows signing config

* fix(desktop): harden vendor signing workflow
2026-08-04 14:41:36 +00:00
ChiGao
5631f4b112
feat(serve): add a required external tool guard provider (#8125)
* feat(serve): add required external tool guard

* fix(serve): keep guard constants off fast path closure

* test(serve): cover guard startup options

* refactor(acp-bridge): centralize external tool guard validation and ack value (#8125)

* fix(core): align MCP reconnect timeout test with safe replay policy (#8125)

The reconnect-on-timeout test still built its mock tools without server
trust or tool annotations, which the safe replay change now requires
before automatically replaying a connection-loss failure. Update the
fixtures the same way the surrounding reconnect tests were updated,
keeping the test's original assertion that a timeout on a known
disconnected server goes through the reconnect path. Mirrors the same
alignment already landed on main.

* fix(cli): alias externalToolGuard subpath for vitest source resolution (#8125)

This PR added `@qwen-code/acp-bridge/externalToolGuard` imports to cli
serve/acp modules but not the vitest source alias every other acp-bridge
subpath carries. Without it, any vitest run whose acp-bridge dist is
stale or absent fails to resolve the import and the five serve test
files die at transform time. Add the alias following the documented
convention in the config so tests read the live source.

* fix(serve): reject non-ASCII external tool guard bearer tokens

A token outside the ASCII range passed construction but made the
handshake throw ERR_INVALID_CHAR when interpolated into the
Authorization header, blocking qwen serve startup in required mode
with an unexplained error. Enforce printable ASCII (0x21-0x7E) at
validation time so the configuration fails fast with a clear message.

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
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: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-04 14:37:13 +00:00
Shaojin Wen
eacc85e846
fix(cli): stop review test-efficacy tests depending on ambient tmpdir vitest (#8537)
Two tests failed on hosts where vitest resolves up-tree from os.tmpdir()
(observed on self-hosted CI where a node_modules above TMPDIR provides
one): findVitestBin's "cannot be resolved" case never threw, and
runControlMutant's "cannot run" case executed the probe for real instead
of throwing.

Make the failure conditions host-deterministic while keeping every
assertion: findVitestBin accepts an injected resolver (default
unchanged) so the MODULE_NOT_FOUND case is forced directly, and the
runControlMutant test plants a shadow vitest whose exports hide
package.json, which wins resolution from any ancestor install and makes
the run fail deterministically.
2026-08-04 14:31:02 +00:00
Shaojin Wen
6e9ecc41e4
fix(core): resolve DashScope thinking-knob conflicts by family (#8488 round 2) (#8536)
Second review round on #8488:

- Honour an explicit extra_body enable_thinking: false on the qwen3.8-max
  family as reasoning_effort: 'none' instead of silently deleting it and
  re-enabling thinking
- Legacy qwen hybrids now drop the inert reasoning_effort override when it
  conflicts with a meaningful thinking_budget, keeping the knobs the model
  actually reads
- Family-gate the pipeline's enable_thinking tool_choice clause like the
  reasoning_effort clause: on non-qwen models sharing the endpoint the
  field is an opaque no-op (GLM reads thinking.enabled), and stripping
  forced tool selection there degraded their side queries
- The tier-native disable path emits reasoning_effort: 'none' — the knob
  the family reads — instead of enable_thinking: false; the
  required-thinking retry trigger recognises the new shape so runtime
  learning still fires
- Warn once per generator (not debug per request) when user extra_body
  knobs are dropped; hoist the wire-model family predicates to
  modalityDefaults.ts and share the provider's extra_body merge tail
- Tests for every behavior above (all load-bearing, verified by targeted
  mutation); docs attribute the vendor rejection to thinking_budget only
  and document the extra_body exceptions
2026-08-04 14:27:08 +00:00
易良
52e0d1b364
feat(web-shell): bind plan approval to its Todo revision (#8393)
* feat(web-shell): gate session workflow behind experimental setting

* feat(web-shell): bind plan approval to todo revision

* fix(cli): clear stale workflow revision on plan entry

* test(web-shell): pin revised workflow snapshot

* fix(cli): clear stale plan revisions on restore

* fix(cli): keep replayed history from rebinding plan revisions

History replay re-sends stale plan updates through Session.sendUpdate,
re-stamping activeTodoPlanRevision from finished plan cycles. Clear the
revision after every replay path (cold replayHistory and live non-bulk
loadSession) so a replayed snapshot can never bind a later exit_plan_mode
approval; reloaded sessions fall back to text-only approval until the next
live todo_write re-establishes the binding. Also drop the bulk-load restore
that could never be read before a plan-mode transition cleared it, and pin
the workflow gates and mode-entry clears with negative tests.

* test(web-shell): pin older plan revision in ChatPane approval test (#8393)

* test(cli): pin unbindable plan updates in approval revision test (#8393)

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

* fix(cli): clear Todo plan revision on history restore (#8393)

restoreHistory was the one history-resetting path that kept
activeTodoPlanRevision, so a restored snapshot could let a stale
revision bind the next exit_plan_mode approval. Clear it like the
sibling reset paths, pin the behavior with a test, and pin the
live-load clear ordering after the replayed updates.

* fix(cli): restore Todo stop guard clear on plan re-select (#8393)

The previous-mode guard added for the revision binding also skipped the
Todo Stop Guard trust clear on a redundant plan re-select; scope the
guard to the revision reset so every transition into plan clears the
stop guard as before. The replay-time revision clears now run in
finally blocks so a transport failure part-way through a replay cannot
leave a replayed binding on the live session, and the web-shell
exit-plan approval rule is unified in one predicate. Revision tests
assert through the observable qwenTodoApproval approval metadata
instead of the private field.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-04 14:25:50 +00:00
Shaojin Wen
d2c7cb1f15
fix(autofix): normalize paginated fetches to one flat array per file (#8438)
* fix(autofix): normalize paginated fetches to one flat array per file

gh api --paginate emits one JSON array PER PAGE, so any PR past 100
comments/reviews/events produces a multi-document stream. The workflow
already slurps correctly in a few readers (jq -rs add, --slurpfile +
add), but more than a dozen plain-jq consumers of the WORKDIR files
mis-aggregate on a multi-doc input:

- MARKERS/REARM_AT/RED_HEAD/REARM_KEY in the scan and their LIVE_*
  mirrors in prepare emit one result per page; ROUND then becomes a
  multi-line string, [[ -ge ]] arithmetic fails, and the round cap
  silently stops holding — on exactly the PRs (takeover, 100-round cap,
  one report comment per round) that reach page two first.
- CAP_NOTICED / BASE_UPDATE_RECENT / LAST_REJECTION / PRIOR_TIMEOUTS /
  the milestone census and the report-step consecutive-failure census
  all degrade the same way.
- NEWEST and LIVE_NEW bind rv/rc/ic/checks POSITIONALLY (.[0]..[3]); a
  two-page rv.json shifts rc/ic into the wrong slots and later
  feedback is silently lost.

Fix at the fetch sites: every --paginate that lands in a WORKDIR json
file (and the report step's COMMENTS_JSON fallback) now pipes through
jq -s 'add // []', so each file holds ONE flat array. Existing
slurp-style readers are unaffected — add is idempotent over a single
array — and every plain consumer becomes correct past 100 items with no
program changes. Failure semantics are preserved: the workflow-level
bash default gives -eo pipefail, so a failed gh still fails the
pipeline exactly where it failed the bare redirect before, and the
pr-events/COMMENTS_JSON fallbacks keep their '[]' paths.

The check-runs/annotations/status-comment reads stay raw on purpose:
they aggregate per-page via --jq + slurp, line-streams, or .[][] and
were already pagination-safe.

Tests: a behavioral case runs the real MARKERS→ROUND pipeline and the
positional NEWEST program against two-page fixtures through the
normalizer, with negative controls demonstrating the pre-fix corruption
(two MARKERS lines; the page-2 review timestamp lost to slot shift).
Shape assertions pin all nine normalized fetch sites and ban raw
--paginate file redirects.

* fix(autofix): pin total --paginate occurrence count in tripwire test (#8438)

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

* fix(autofix): correct gh --paginate merge model in pagination comments (#8438)

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

* fix(autofix): make engage-ack ic re-fetch atomic on failure (#8438)

* test(autofix): pin atomic engage-ack re-fetch and empty-input normalization (#8438)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-04 14:14:52 +00:00
Shaojin Wen
ac67de2e46
fix(review): stop the reverse-audit loop while there is still time to report (#8468)
* fix(review): stop the reverse-audit loop while there is still time to report

Measured on CI run #8368 (+1699 lines): the iterative reverse audit ran to
its 5-round cap, each round a per-chunk fan-out whose findings then went
back through verification, and the loop consumed 3.5 of the job's 4
budgeted hours. The outer GNU-timeout kill arrived while round 5's
findings were still being verified. The review died holding every
confirmed finding it had; nothing reached the pull request.

The loop's rounds are driven by the orchestrator, but every round begins
at the same place: agent-prompt building the round's prompts. So the
builder becomes the loop's clock. When the environment carries a review
deadline (QWEN_REVIEW_DEADLINE_EPOCH, exported per attempt by the review
workflow) and the remaining time is inside the reserve kept for the last
verification, compose-review and submission (default 60 minutes,
QWEN_REVIEW_DEADLINE_RESERVE_SECONDS to override), a reverse-audit round
is refused: a BUDGET line on stderr, exit code 4, no prompt built and no
record written. The message carries the exact unreviewedDimensions entry
to file, so the disclosure that caps the verdict is the CLI's text, and
Step 6 proceeds with the findings already confirmed.

Local runs have no deadline and are untouched. A malformed deadline fails
open — the outer kill still bounds the run, and a broken variable must
degrade to today's behaviour rather than wedge every budgeted review at
round 1. The verifier is deliberately not gated: the reserve exists so it
can run.

* fixup: scale the deadline reserve to the externally-chosen budget

The budget is not this workflow's to assume: it arrives from a repository
variable, a workflow input, or a /review --timeout=N comment. A fixed
60-minute reserve would consume most of a 70-minute budget and refuse the
audit loop outright on a 30-minute one. The workflow now passes a reserve
of a quarter of the attempt, floored at 10 minutes and capped at 60; the
CLI constant remains only the fallback for a caller that sets a deadline
without a reserve.

* review feedback: admit the round only if IT fits, and cap deterministically

Three findings from review, all taken:

1. The gate budgeted for the tail but not for the round it admits — the
   terminal round is by construction the one that starts closest to the
   boundary, so the killed-mid-verification failure survived one round
   wide. The gate now requires remaining >= round + reserve, where the
   round's cost is the previous round's, measured admission-to-admission
   from a stamp the builder writes (one per round; a same-round rebuild
   is not a round), falling back to a 30-minute constant for round 1,
   which starts with the most headroom.

2. The refusal was deterministic; the disclosure that caps the verdict
   was prose the orchestrator had to carry. The builder now records a
   budget-stop marker beside the prompt records and compose-review
   synthesizes the unreviewedDimensions entry from it — deduped against
   a relayed copy — so a run that drops the sentence still cannot
   approve past a truncated audit.

3. Exit code 4 is documented in the command's describe.

Also restores the Step 5 bullet the previous commit's edit displaced
(new findings merge into the cumulative list before the next round).

* review feedback: pin the budget gate's all-chunks refusal and ordering

Cover the two behaviours the review noted were only asserted on the bare
--findings form: an exhausted budget refuses the loop's real
--all-chunks round before ANY of the per-chunk records is written, and
a malformed call (--round 0) still gets its validation error first —
exit 4 is for a well-formed round the budget refuses, never a
replacement error. Also name what the code already does: reserve=0 is
the deliberate escape hatch (the gate shrinks to the round estimate
alone), and the workflow's 3600s cap mirrors DEFAULT_RESERVE_SECONDS.

* docs(review): describe the soft-deadline env vars for time-budgeted runs

The review noted the two new variables appeared in no user-facing doc;
the reserve in particular is an operator-facing knob. State what each
does, the fail-open posture, and how the refusal surfaces in the verdict.

* fix(cli): align budget-stop disclosure with the gate's refusal (#8468)

A round-1 budget refusal left no reverse-audit records, so the Step 4/5
floor reported the deliberate stop as a rogue/unlaunched audit with a
rebuild FIX the same gate deterministically rejects; the refusal's own
disclosure was swallowed by the caller-echo dedup. The floor now stands
down when the budget-stop marker exists, and compose-review renders the
disclosure structurally, bilingually, from the marker.

Also: `--role reverse-audit` requires `--round <k>` (an unlabeled
admission stamps an entry no estimate can attribute), the budget gate
runs after the plan/findings reads (a broken plan or unreadable findings
deserves its own error, and nothing is stamped ahead of a buildable
call), and the gate's admission boundary, measured-cost behaviour, and
the workflow env contract are pinned by tests.

* review: a budget stop excuses only the round it refused

The budget-stop suppression keyed on the marker's existence alone, so
every reverse-audit gap shape went silent once any round was refused —
including the shapes that describe rounds which RAN before the budget
hit. A hand-written round-1 launch is exactly as undelivered when round
3 later hits the budget, and suppressing its disclosure let 'stopped
before round 3' imply the rounds that did run were faithful.

Exactly one shape is by design under a marker: not-built — the refusal
writes no record, so an audit with no records IS the audit the gate
stopped, and its FIX (rebuild the round) would be refused by the same
gate. The suppression now names that shape and no other; a rewritten,
unlaunched or brief-unread round keeps its disclosure and its repair.

The new test pins the operative halves: the verdict stays capped, the
marker's disclosure posts, and the operator channel carries the
rewritten round's exact repair. (The posted body collapses same-subject
disclosures — both say 'reverse audit' — so the author sees the stop;
repairs are acted on from stderr, where the rewritten fix rides.)

* fix(review): fence budget state per run, and let gate errors beat budget stops

Address the round-2 review threads on the reverse-audit budget gate:

- Fence budget-rounds.json and budget-stop.json by the plan's own mtime.
  Every run rewrites the plan at its Step 1 capture, so records older than
  the plan belong to a previous run of the same PR: a run killed before
  cleanup no longer prices the next run's rounds off stale stamps (an
  hours-old stamp read as an hours-long round refused round 1 of a fresh
  budget) and no longer caps a later run's verdict on a stop that did not
  happen in it (R2-1, R2-2).
- Refuse a structurally unbuildable plan (no chunks[], duplicate or
  non-integer ids) with its own error ahead of the budget gate, so the
  same corruption gets the same diagnosis whatever the clock says, and no
  budget-stop marker is written over a corrupt plan (R2-5).
- Stamp a round admitted only after its build succeeds: a build that
  throws leaves no stamp, so the next round's cost is never measured from
  a build that produced nothing and floored to 600s (R2-6).
- Keep the budget entry's 'reverse audit' subject out of the caller-echo
  prefix filter: other reverse-audit scopes the orchestrator disclosed
  (a twice-whiffed chunk from the rounds that DID run) are no longer
  silently dropped in the marker's shadow; the marker's own relays stay
  deduped by the phrase splice (R2-7).
- Render --round unbracketed in the reverse-audit rebuild fix — the CLI
  refuses a round-less reverse-audit call, so the paste-and-run repair
  must not present the flag as optional (R2-14).
- Document the deliberate one-verification overlap between the measured
  round estimate and the tail reserve, at both definitions (R2-13).
- Test hardening, each assertion mutation-probed to fail its named
  mutant: a reshaped relay only the marker-phrase splice dedups (R2-8);
  the stamp's round label and the verifier's no-stamp invariant (R2-9);
  whole-line, unit-arithmetic and reserve-cap pins on the CI wiring
  contract (R2-10); the first-wins stamp survivor (R2-11); the reserve=0
  escape hatch (R2-12).

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-04 13:58:15 +00:00
Shaojin Wen
56baa7af7b
perf(review): the review-round refinements for the setup batch (#8487)
The batched-setup paragraph merged to main through #8499's squash of its
base branch, without the two review rounds that followed on #8487. This
re-lands those refinements as the delta against main's current text:

- 'ONE response' now says separate tool calls, never an &&/;-joined
  Shell chain — a chain changes the failure semantics (pr-context
  failing must warn-and-continue, not skip the other two) and merges
  the warning: size lines the paging decisions read.
- The rules load names its ref uniformly (<remote>/<baseRefName>, the
  ref fetch-pr just updated) with the baseFetchFailed carve-out spelled
  in both Step 1 and Step 2, replacing the prefer-local probe language —
  deciding 'does <base> exist locally' costs exactly the serial turn the
  batch removes, and an unresolvable ref makes load-rules report 'no
  rules found', indistinguishable from a repo that has none.
- The incremental-cache read pairs with the fetch-report read where the
  read is instructed (both read_file, genuinely parallel).
- The executable-script lint's enumeration counts file reviews — they
  have a tree and scriptLintGate owes them the lint; the adjacent gate
  note already said so.

Co-authored-by: verify <verify@local>
2026-08-04 13:56:19 +00:00
Shaojin Wen
874e46d734
fix(core): harden Qwen 3.8 reasoning effort wire shape (#8488)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(core): harden Qwen 3.8 reasoning effort wire shape (#8472 follow-up)

Follow-up to #8472, addressing the post-merge review findings:

- Drop enable_thinking/thinking_budget after the extra_body merge whenever
  reasoning_effort ships: the Token Plan preset made qwen3.8-max-preview
  carry both thinking knobs, and DashScope rejects reasoning_effort
  combined with thinking_budget
- Family-gate the new tool_choice=required strip clause to qwen wire
  models: reasoning_effort is an opaque sampling override on non-qwen
  DashScope models, and dropping forced tool selection degraded their
  structured side queries
- Prefix-match the qwen3.8-max family so dated snapshots and -latest
  aliases receive the selected tier instead of silently collapsing to
  enable_thinking
- Log the tool_choice strip; restore the effort-config JSDoc and comment
  the request-level override copy

* fix(core): family-gate DashScope thinking-knob drop (#8488)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-04 12:42:02 +00:00
Shaojin Wen
10291e16f2
refactor(core): move review skill incident narratives to DESIGN.md (#8499)
* perf(review): issue independent setup calls in one response

Measured on a real small-PR run: the stretch from parse-args to the
first agent launch took 7 minutes of wall clock, one round-trip at a
time, on calls that never needed an order — pr-context, comment-status
and the Step 2 rules load are mutually independent reads. Step 1 now
tells the orchestrator to issue all three in a single response (the
same rule Step 3 already enforces for the agent fan-out) and to page
their outputs in shared responses too. comment-status loses its
wait-for-the-context-file guard in worktree mode: learning whether
inline comments exist cost a serial round-trip, while running it on a
commentless PR just writes an empty index. Step 6's two deterministic
gates (script-lint, test-plan) get the same one-response note.

The orderings that matter are kept explicit: fetch-pr before everything
(it creates the worktree and the plan), the roster after the rules load
(it bakes the rules into every brief).

* refactor(core): move review skill incident narratives to DESIGN.md

SKILL.md is injected wholesale into the review orchestrator's context on
every /review run and re-billed on each of its turns, and ~16KB of it was
incident narrative — accounts of past dogfood failures and measurements
that justify rules but are not themselves instructions.

Move 50 such narrative blocks into a new 'Measured incidents (moved from
SKILL.md)' section of DESIGN.md (47 anchors, not loaded at runtime),
leaving every rule in place with a short
'(measured; DESIGN.md — <anchor>)' pointer. Force-bearing figures stay
inline where the number is the argument (e.g. the ~161s cold npm ci, the
41% test-code median, the PR #6457 one-of-five checklist measurement).

No instruction, gate, format, flag, threshold, or ordering changed; the
YAML frontmatter and all 35 fenced code blocks are byte-identical, and
the MUST / Do not / never imperative counts are unchanged outside the
moved narrative text (verified by script).

SKILL.md: 237,847 -> 228,266 bytes; DESIGN.md: 106,708 -> 125,184 bytes.

* fix(review): keep DESIGN.md out of the runtime bundle and pin pointers

The slim refactor left DESIGN.md shipped beside SKILL.md in
dist/bundled/, so one curious read_file of the 125 KB maintainer
document would cost more context than the refactor saves. The bundle
copy now skips DESIGN.md, and SKILL.md gains a one-line guard telling
the orchestrator the pointers are for humans auditing a rule.

Also addresses review feedback: a test pins both directions of the
SKILL.md incident-pointer mapping, the transcribed-argument narrative
keeps its referent after the move, the incidents section title loses
its changelog suffix, and Step 2 no longer asks for a base fetch that
fetch-pr already performed.

* fix(review): gate setup batching by effort and consolidate incident blocks

Address round-1 review feedback on the skill-slim PR:

- Gate the ONE-response setup batch and the comment-status call to high
  and medium effort, matching Step 2's low-effort skip.
- Scope the Step 6 lint/test-plan batching to same-repo PR reviews.
- Merge same-run incident blocks (self-composed Approve into the
  paraphrased roster prompt; archive verdict into the narrated-away cap),
  cross-reference the roster-size and relocated-Critical tellings, and
  state the #8368 path in its block plus the pointer it was missing.
- Pointer-ize the last inline QQChannel narrative and fix the
  scripts-nobody-ran summary to match its block.
- Extend the DESIGN.md exclusion to copy_files.js so the transpiled
  dist/src build and the published core tarball stop shipping it.
- Pin the no-read_file guard and the batch ordering constraints in
  SKILL.test.ts, and fail loudly on pointers the regex cannot parse.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-04 12:41:18 +00:00
C0d3N1nja97342
e7362d5d08
fix(web-shell): add explicit ::selection for message content in Firefox (#8417)
* fix(web-shell): add explicit ::selection for message content in Firefox

Firefox does not paint the default selection highlight for text whose
element chain passes through a display:contents element (the
data-user-selectable wrapper on MessageItem). The logical selection
(copy, selectionchange popup) works fine - only the visual highlight is
missing. An explicit ::selection background makes Firefox paint the
highlight where the default painting fails.

Fixes #8214

* fix: use fixed color instead of non-existent CSS variable

--selection-bg was never defined in the codebase (only
--chat-editor-selection-bg exists in App.module.css). Use a fixed
hsl(210 100% 50% / 30%) to avoid confusion.

* test(web-shell): pin ::selection rule and soften root-cause framing

Reframe the standalone.css comment and PR description as a defensive
workaround, not a confirmed root-cause fix: the data-user-selectable
wrapper is shared by user and assistant rows, and the reporter's
screenshot shows an embedding-page toolbar this package does not ship.

Add a getComputedStyle(..., '::selection') assertion to the smoke e2e so
a future cleanup cannot silently drop the rule.

* fix(web-shell): move ::selection rule to component-scoped globals.css

The defensive ::selection rule for [data-user-selectable] message content
was in standalone.css, which is only loaded by the standalone app entry
(client/main.tsx) and the e2e harness. The npm package entry
(client/index.tsx via vite.lib.config.ts) never loads standalone.css, so
embedded deployments of @qwen-code/web-shell - including the reporter of
#8214 - did not receive the rule and still saw no selection highlight.

Move it to globals.css, which is imported by App.tsx and
WebShellTranscript.tsx and therefore ships with the component-scoped
stylesheet. Verified against the lib build: the rule now appears in
dist/index.js correctly scoped under
[data-web-shell-root][data-web-shell-shadcn]. The standalone app also
loads globals.css, so the e2e smoke pin still passes.

Addresses the review finding on standalone.css:119.

* test(web-shell): pin ::selection across all rows and in the lib bundle

Address review findings on the round-3 move to globals.css:

- The smoke e2e only sampled the first [data-user-selectable] row (the
  user row in this fixture). Assert the rule on every selectable row so
  a future narrowing to user rows keeps assistant rows covered.
- Nothing asserted the rule survives in the npm lib bundle - the
  deployment this fix exists for. Add a build-artifact test that parses
  the injected component CSS in dist/index.js and pins the scoped
  [data-user-selectable] ::selection rule under [data-web-shell-root].

* test(web-shell): assert ::selection on selectable wrapper rows, not descendants

Per review: querySelectorAll('[data-user-selectable] *') counts element
descendants, not the wrapper rows themselves - a single user row renders
4+ descendants, so the >=2 invariant did not actually enforce that both
roles are present. Match the [data-user-selectable] wrappers directly and
sample one descendant per row.

* test(web-shell): match ::selection lib-bundle pin by effect, not notation

Per review (R6-1): the pin matched an exact selector substring
(including the space) and an exact prop name, coupling to the current
notation. A maintainer switching 'background' to 'background-color'
(the CSS Pseudo-Elements-4 name) would fail this test with a misleading
message while the e2e pin stayed green. Match the two selector halves
independently and accept either prop name.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-04 12:33:51 +00:00