mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-10 17:27:10 +00:00
8399 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8c90697ace
|
test(memory): Cover context refresh marker carry-over turns (#8809)
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 11 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (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
* test(memory): cover context refresh carry-over turns * test(memory): pin continuation turns and dedupe carry-over test helpers * test(memory): drop mockClear from shared carry-over marking helper * test(memory): guard against unregistered onComplete in tool-write helper --------- Co-authored-by: 俊良 <zzj542558@alibaba-inc.com> |
||
|
|
bdb7e418ba
|
chore(release): v0.21.9 (#8886)
* chore(release): v0.21.9 * docs(changelog): sync for v0.21.9 --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
9bbb11ac32
|
fix(review): make the posted review body readable (#8825)
* fix(review): make the posted review body readable The posted body on a capped COMMENT joined every clause with spaces: on #8388 that ran 31 unresolved existing Criticals and seven disclosures into one paragraph, each entry restating the same truncation reason, every comment id a bare number, and the Chinese fold duplicating the whole untranslated wall. - Render the COMMENT body as paragraphs: the short verdict sentences stay one opener paragraph, every disclosure gets its own (matching the REQUEST_CHANGES branch). - Render the unresolved-Critical block as a Markdown list, grouping entries that share the exact reason after their first " — " so the reason is stated once over a sub-list of subjects. Nothing is dropped: every subject and every distinct reason still renders. - Linkify "comment <id>" / "issue-level comment <id>" in those entries to their #discussion_r / #issuecomment anchors when the plan names the PR. - The Chinese fold no longer duplicates the untranslated English list; it carries the count and points at the English half. - budgetGapDisclosures: a parenthesized placeholder ("(none — all planned checks completed)") now reads as a placeholder — the leading "(" defeated the leading-token match, and #8388's body disclosed it as a self-contradictory "Not explored to full depth" entry. * fix(review): harden the readable review body per review feedback - budget disclosures: the paren strip now drops only the bare placeholder and the completion idiom, so a real gap that merely starts with a placeholder token ("(none of the chunk-2 checks ran — the runner died)") survives instead of being silently dropped before the coverage gate; duplicate folding keys on the paren-stripped text so a gap restated with and without parens discloses once - compose-review: one shared plan-identity rule for the bilingual recovery and the comment anchors (the two copies had diverged); anchors follow the plan's recorded GHE host with a relaxed id floor off github.com; cannot-tell entries flatten embedded newlines and a dangling " — " reads as reasonless instead of an empty group key - tests pin all of the above, including the previously untested already-linked passthrough, the reasonless shape, and the bilingual fold count beyond N=1 * fix(review): keep genuine budget gaps and harden comment anchors - budget disclosures: the completion idiom now requires an actual completion predicate the text ends with — a head word alone ("none — all 5 Windows checks failed to start") no longer reads as done — and the template/dash alternatives are end-anchored, so an inner text merely starting with "<…>" or a dash run keeps. One strict classifier judges the paren-stripped text, bare and wrapped alike, ending the divergent two-parser treatment of identical content ("none of the chunk-2 checks ran — the runner died" was kept wrapped, dropped bare). - budget parse linearity: the placeholder classifier, the gap-line regex, and the trailing-punctuation strips no longer carry overlapping quantifiers — a token followed by a long whitespace run was measured quadratic (seconds at 40k spaces); every shape is now linear by construction and pinned by the linearity test. - comment anchors: the effective host is normalized (case and the implicit :443) before the github.com short-id floor comparison; "issue-level" reads case-insensitively, matching the pipeline's own capitalized label; a plan ownerRepo carrying "."/".." segments is rejected before it rides into a posted URL. - tests: the plan fixture carries the PR identity instead of four pasted read/modify/write backdate dances; the anchor suite is hermetic against an exported GH_HOST and now pins the host-fallback, host-rejection, ownerRepo-rejection, routed-host, prefixed-cannot- tell, and cased-Issue-level branches; the Chinese fold assertion covers the pointer, not just the count. * fix(review): address round-3 review findings on gap classification and anchors - End-anchor the stayed-under-budget idiom in PLACEHOLDER_GAP_RE: text continuing past "budget" discloses skipped work and must keep (R3-1). - Require the completion word to be affirmed — a negated tail ("none completed", "nothing finished") is a failure report, not completion (R3-2) — and temper the completion spans so an exception quantifier ("all but X completed") keeps the gap (R3-3). - Close the drop-side gaps: "everything" as a completion head, the found / to-report non-answer idioms, and inner paren padding (R3-5); pin the whole drop vocabulary and the new keep shapes (R3-6). - Collapse multi-line cannot-tell entries by split/join and guard stripReviewFooter on the footer marker: both regex paths scanned quadratically on long whitespace runs in model-written entries with no length cap (R3-4; the footer strip measured ~20 s at 80k characters). - Decide the anchor family per entry, not per match: an issue-level entry echoing pr-context's own header shape carries its id apart from the phrase, and adjacency-routing minted #discussion_r anchors in the wrong id space; link text now keeps the entry's casing (R3-8). - Normalize www./trailing-dot/zero-padded-port variants of github.com before the short-id floor comparison (R3-9). - Lift the dot-segment-aware owner/repo rule into lib/gh.ts as isOwnerRepo — submit's --repo check and the plan identity now share one copy (R3-10) — and revert cleanup.ts's OWNER_REPO_RE export. - Pin the BUDGET_GAP_LINE_RE rewrite's linearity and bullet handling (R3-7), the corrupt-plan degradation (R3-11), and the numeric prNumber arm of the plan identity (R3-12). * test(review): pin the gap-line linearity gate with a 160k-space probe --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
fbccb3eac0
|
fix(cli): 'later' on provider update persists a cooldown instead of re-prompting every launch (#8829)
* fix(cli): 'later' on provider update persists a cooldown instead of re-prompting every launch Choosing "Remind me later" (or pressing Esc, which maps to 'later') previously persisted nothing, so the Built-in Provider Update prompt reappeared on every startup whenever the built-in model list version differed from the stored metadata version. Users upgrading to a release that changes the Token Plan model list were nagged each launch. Now 'later' persists postponedVersion + postponedAt. findAllPendingUpdates skips re-prompting while the 24h cooldown is active for the same version. A new model-list version (postponedVersion mismatch) still re-prompts immediately, preserving the "remind me later" meaning. 'skip' (per-version ignore) and 'update' behavior are unchanged. * fix(cli): batch the 'later' cooldown persist and cover the read-side guard (#8829) * fix(cli): expire the 'later' cooldown on backward clock jumps (#8829) --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
b4f104fb6f
|
[webshell]retry session switch while closing (#8864)
* fix(webui): retry session load while closing * fix(webui): settle closing-session retry races |
||
|
|
b2404836ba
|
fix(cli): give sandbox containers collision-proof names (#8880)
The regular-run container name was allocated by counting docker ps -a and taking the first free index — a check-then-run window with nothing holding the name between the count and `docker run --name`. That was survivable while launches on a shared docker daemon were rare, but the CI pool packs several runner registrations per host, and a dense autofix scan launches sandboxes on sibling registrations within the same second: in run 31389561905, SEVEN of fourteen legs lost the race — each got `docker: Conflict. The container name "/qwen-code-0.21.8-0" is already in use` (exit 125) one second into its agent step, burned a round, and pushed items toward their round caps. A random 8-hex suffix replaces the counter, the same scheme the integration-test branch has always used. Nothing predicts the name: run-agent.mjs parses it from the `ContainerName (regular):` line, and the stale-container reaper matches on the image-name prefix, which the suffix preserves. The docker ps call goes with the counter — one less execSync on the launch path. Co-authored-by: verify <verify@local> |
||
|
|
5dc98240c7
|
fix(ci): port the verify gate's remaining hardening from #8765 (#8878)
#8816's branch accidentally carried #8765's early commits, and the takeover loop evolved the gate further there (subset identity via comm -23, the retryable third arg, subset fixtures) — so #8765 closes as subsumed, and this PR ports what main still lacks: the two improvements its reviewers named for porting, plus the open round-6/7 findings that survive on main's gate. - Pre-detach short-circuit: an empty head signature (vite/esbuild/ crash — the KNOWN LIMIT class) fails closed regardless of the baseline, so decide it BEFORE paying the detach + full baseline re-run + restore. - Build-dirt guard: the A/B'd build REWRITES a tracked file (the vscode companion settings schema), and the undiscarded rewrite makes either checkout refuse — degrading a real verdict into the crash path. `git restore -- .` before both checkouts; tracked-only, and the tree was asserted clean before the deterministic checks. - Restore-failure semantics: a plain outcome=failed is an EVALUATED rejection — the watermark advances and a transient git failure strands the item as a permanent human handoff. The gate now leaves outcome unset (the gate-crashed path retries next scan) and still writes the detail document so the crash comment explains itself. - The dist-rebuilt steering note seeds the repair feedback on both retryable A/B exits — the repair agent's only warning that dist/ holds baseline-built artifacts. - The stale-base retry handoff prefixes its embedded rejection with a the-base-has-moved note, so the retry agent is not steered toward no-action by framing written before the auto-update. - The two A/B side logs joined the repair step's cleanup list. - Tests: identity-less short-circuit, tracked-dirt survival, verdict-less restore crash, long-preamble render cap, PREEXISTING clause selection through the executable report harness, and the stale-framing note pin. Mutation-tested, 5 of 5 caught: short-circuit dropped, restore guards dropped, restore-failure reverted to the evaluated rejection, dist note dropped, stale-framing note dropped. Co-authored-by: verify <verify@local> |
||
|
|
95e17691a9
|
chore(serve): remove the /demo debug page (#8805)
* chore(serve): remove the /demo debug page The daemon has shipped a real browser UI for a while: `resolveWebShellDir()` finds the bundled Web Shell assets and `mountWebShellAssets()` serves them at `/`, so `qwen serve` already opens onto a full client. `/demo` stayed behind as a 663-line inline-HTML console covering the same ground with none of the reach — nobody drives the daemon through it, and `npm run dev:daemon` starts the Web Shell dev server rather than the demo page. Keeping it around costs more than the dead code. It is the only file in the tree that pairs an event log with daemon HTTP, so work that starts as a Web Shell observation lands there instead: #8762 was found while running `/review` through the Web Shell and was fixed entirely inside the demo page's rendering, with "no Web Shell changes" in its own risk note. Deleting the page removes that decoy. Nothing is lost for protocol-level debugging: `GET /session/:id/events` streams the same raw frames the Events tab printed. `/health` shared `routes/health-demo.ts` with the demo handler, so the module is now `routes/health.ts` / `createHealthRoutes()` and drops its `getPort` dependency. The rate-limit exemption, the boot breadcrumb, and the daemon docs lose their `/demo` arms; the loopback self-origin shim regression test already asserted through `/health` and only needed its title corrected. * test(serve): pin the removed /demo contract and the pre-auth surface Review follow-up. Three of the removal hunks shipped ungated, and two doc sentences the removal rewrote were describing the pre-auth surface wrong — both before and after the edit. Deleting the `/demo` route took its assertions with it, so nothing failed if the handler came back: the Web Shell suite only exercised a generic deep link, and the rate-limit exemption could be widened again with the suite still green. `/demo` is now pinned as what it became — an ordinary unknown path: a non-navigation request 404s, a browser navigation is answered by the SPA fallback like any other deep link, and once a token is configured (with or without `--require-auth`) that navigation is refused with 401, because the fallback sits behind the bearer. The rate-limit test pins that `/health` is the only exempt GET, so re-adding a second pre-auth page to the predicate fails instead of silently escaping the limiter. Each new assertion was checked by reverting the hunk it guards and confirming it goes red. The `--allow-origin '*'` warning and both `--allow-origin` doc paragraphs enumerated `/health` as the residual tokenless surface and said nothing about the Web Shell static assets, which are mounted before the bearer in every launch mode and stay reachable even under `--require-auth` — the enumeration also claimed `/health` stays pre-auth on non-loopback binds, where it is registered behind the bearer and 401s. A probe across all three launch modes established the actual matrix; the warning and the docs now match it and name `--no-web` as the way to remove the residual browser surface. The warning text is asserted by a test for the first time. * fix(serve): correct Web Shell doc claims and re-pin the pre-auth CORS wall Review follow-up. The removal rewrote the daemon docs around the Web Shell, and three of the rewritten claims did not match what the runtime actually does: §1 never said how the bearer reaches the browser (with auth on, the plain URL loads a shell whose every API call 401s), §8 called the shell writable on any bind (on a non-loopback bind without `--allow-origin` its POSTs hit the CORS wall and 403), and §8 served `/session/:id` without the document-navigation qualifier its own code enforces. The §9 call-chain diagram also still listed the deleted `/demo` route, the developer flag references had no `--web`/`--no-web` row despite the new guidance pointing at the flag, and both design docs listed the JSON body parser ahead of post-auth `/health` while `createServeApp()` registers them the other way round. The deleted `/demo` CORS test was also the only assertion that a pre-auth page sits behind the Origin wall — every surviving Origin test targets an API path. Re-pin it for the shell root so a mount-order regression fails instead of exposing the pre-auth HTML surface cross-origin. * fix(serve): finish demo rename sweep and scope pre-auth shell claims to loopback 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-Coder <qwen-coder@alibabacloud.com> |
||
|
|
4cf943219d
|
fix(cli): keep the selected model when refreshing another provider's template (#8868)
* fix(cli): keep the selected model when refreshing another provider's template A provider template refresh reuses the first-time-install plan, which always carries a default "select the first model" instruction. The refresh acted on it whenever the current model was missing from the refreshed list — and a model owned by a different provider is always missing from it, so accepting an update silently rewrote model.name and cleared model.baseUrl. Leave the selection alone when the current model is installed under the same protocol but owned by another provider; a model that is not installed at all is still migrated. Also stop reporting "Model switched to <old model>" when the active model did not actually change. Fixes #8863 * fix(cli): preserve auth and models on provider refresh |
||
|
|
7f61e03b82
|
perf(ci): make the triage budget operator-tunable and raise it (#8810)
* perf(ci): make the triage budget operator-tunable and raise it The triage job's fixed 30-minute cap was killing every substantial PR's triage at exactly the budget — five observed (8723, 8726, 8731, 8764, 8801), each a full agent run discarded — and the successful-run distribution shows why: 22 jobs sampled ran median 5.8m / p90 22.3m / max 29.5m. A max just under the cap is a distribution being clipped, not one that fits. Back the budget with QWEN_TRIAGE_TIMEOUT_MINUTES (fallback 60), the same pattern as the autofix fleet caps in #8731 and probe-verified there for expression-valued job fields. Triage is advisory: the worst case a longer budget risks is one held runner, while a killed run wastes the same runner and the work it was about to publish. Pinned by a test asserting the variable-backed form with its fallback; reverting to the fixed literal fails it. * Address review: sanitize the knob, pin the active line Both findings verified before fixing. The knob was unvalidated, and the failure mode is nasty precisely because the knob exists to be changed WITHOUT a PR: a malformed repository variable — '60 minutes', '1h', a 6O typo, quotes, a decimal — would make fromJSON throw at expression time, killing the triage job before any step runs: no always() finalize, no status comment, a silent stop whose run log blames fromJSON rather than the variable. And '0' parses fine into an instantly-cancelled job. timeout-minutes cannot be validated by the job that consumes it, so the authorize job (which triage already needs, and which can run bash) sanitizes it: bare-integer check, floor 10, ceiling 600, every rejection warning with the VARIABLE's name. The triage job consumes the sanitized output; || 60 covers the events where authorize is skipped. The pin was an unanchored substring over raw YAML, satisfied by a commented-out line while the job silently inherits GitHub's 360-minute default — the review probe-verified that mutant surviving. The pin now matches active lines only, and the sanitize step is replayed verbatim against the review's whole enumeration: unset, sane, zero, runaway, and five malformed shapes, each asserted to fall back and name the knob. Mutation-tested, 3 of 3 caught: the timeout line commented out, the floor clamp dropped, the integer check bypassed. * fix(ci): clamp over-long triage budgets before bash arithmetic (#8810) A repository variable with more than 18 digits overflows bash's 64-bit arithmetic, and the wrapped residue can land inside [10,600], silently skipping both clamps with no warning — the one malformed shape where the sanitizer's diagnostics go dark. Check the digit count before the arithmetic and clamp such values to the ceiling with a warning. Also pin the seams the replay test cannot see: which repository variable feeds RAW, that the sanitize step is unconditional, the exact clamp boundaries, the 10# decimal guard, and the overflow clamp. * fix(ci): strip leading zeros from triage budget before the width guard (#8810) * fix(ci): pin triage timeout seams on parsed YAML and runner shell flags (#8810) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
c1a6b2daf5
|
fix(cli): require exact daemon marker before env scrub (#8811)
* fix(cli): require exact daemon marker before env scrub * test(cli): isolate daemon marker cases |
||
|
|
a292c89a3b
|
feat(cli): add background cleanup for OpenAI API logs (#8862)
* feat(cli): add background cleanup for OpenAI API logs With model.enableOpenAILogging on, every API call appends a full request/response JSON under logs/openai with no rotation — heavy usage accumulates hundreds of thousands of files (tens of GB) within months. Register a third cleaner in the existing background housekeeping pipeline that sweeps openai-*.json files older than the new model.openAILogRetentionDays setting (default 7 days). The filename-embedded UTC date is used as a fast path to avoid one stat() per file; the boundary day and unparseable names fall back to mtime. Throttling is keyed on the resolved log dir, so both the default per-CWD layout and a shared custom openAILoggingDir are swept at most once a day. The sweep runs regardless of whether logging is currently enabled, so residue from earlier debugging sessions still gets cleaned. Scope note: housekeeping only starts for interactive sessions, so headless (-p) / SDK processes are not covered yet. * codex: address PR review feedback (#8862) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
fa8cae5418
|
fix(serve): Allow approved external built-in text writes (#8852)
* fix(serve): allow approved external built-in text writes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): keep write provenance off startup bundle Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
4bc75c2bdb
|
fix(cli): list blocking background tasks in session-switch errors (#8742)
* fix(cli): list blocking background tasks in session-switch errors When /clear, /resume, or /branch is blocked by running background work, the error only said "stop your tasks" without naming them. Users who forgot a long-running background shell (e.g. a dev server started a day earlier) had no way to connect the block to a specific task unless they already knew /tasks exists. Enumerate the blocking entries (id, label, status, runtime) in the error and point at /tasks or /workflows depending on which kinds block. The gate itself is unchanged; the message falls back to the bare base text if an entry settles between the gate check and enumeration. * fix(cli): cap blocked-entry enumeration and trim redundant surface Post-review polish for the #8741 change: - drop the redundant count field (lines.length carries it) - cap enumeration at 10 lines plus a "…and N more" overflow tail - type the collected status with TaskStatus | WorkflowStatus - collapse duplicated test assertions and narrating comments * fix(cli): flatten newlines in blocking-entry labels Review finding (security, P3): stripUnsafeCharacters preserves LF/CR, so a multi-line label (heredoc shell commands, workflow meta from files) could split one entry into forged extra lines inside the blocked message. Flatten CR/LF to spaces when building each line. * fix(cli): tighten blocking task details |
||
|
|
7c89665be1
|
fix(cli): extend the #8663 loader denylist and harden its scrub lifecycle (#8763)
* fix(cli): extend the #8663 loader denylist and harden its scrub lifecycle Follow-up to #8663. Its inherited-env denylist closed the NODE_OPTIONS/ NODE_PATH class but left sibling code-execution and TLS-trust-anchor vars that reach the same #8653 cross-workspace outcome — an untrusted workspace `.env` is frozen into daemonRuntimeBaseEnv and distributed to every workspace's session subprocesses. Denylist additions, split by the PR's own tiering: - Scrubbed loader tier (INHERITED_LOADER_ENV_KEYS — scrubbed from the inherited launch env and rejected from every `.env`/settings.env scope), for pure-injection vars with no legitimate operator-shell use: OPENSSL_CONF (startup dlopen of an attacker OpenSSL engine), NODE_REPL_EXTERNAL_MODULE, npm_config_node_gyp, npm_config_init_module. - Reject-from-project-`.env` tier (PROJECT_ENV_HARDCODED_EXCLUSIONS — rejected from project files, preserved from the shell / home `.env`), for vars with a legitimate operator-shell use whose only exposed vector is an untrusted project file: * TLS trust anchors SSL_CERT_FILE, SSL_CERT_DIR, CURL_CA_BUNDLE, REQUESTS_CA_BUNDLE, GIT_SSL_CAINFO (siblings of NODE_EXTRA_CA_CERTS; an attacker CA MITMs a session's git/npm/pip/curl traffic). * git command-execution family GIT_SSH_COMMAND, GIT_EXTERNAL_DIFF, GIT_CONFIG_GLOBAL/SYSTEM/COUNT and the numbered GIT_CONFIG_KEY_<n>/ GIT_CONFIG_VALUE_<n> pairs (matched by prefix). core/utils/git-branches.ts already scrubs these from the repo's own git invocations. * node-gyp interpreter selection NODE_GYP_FORCE_PYTHON, npm_config_python, PYTHON (run as the build Python during native-addon installs). Concurrency: the daemon's process.env scrub/restore and the loader-key rejection reporter were process-global with no guard for concurrent embedded daemons in one process (a documented supported config). The first daemon's close() restored loader vars into the shared env, re-poisoning a still-live sibling's sessions, and dropped its reporter. The scrub is now reference counted (acquireInheritedLoaderEnvScrub — snapshot on first acquire, restore only on last release) and the reporter is cleared only when still active. Test hardening from the same review: pin the daemon-worker scrub breadcrumb (not just key removal); pin the fast-path settings.env case-folded hardcoded-exclusion gate; drain the module-global fast-path stash so the accumulate assertion is order-independent. Docs updated for the new keys. * fix(cli): keep the loader-scrub process.env access in the serve guard surface The refcounted acquireInheritedLoaderEnvScrub read/wrote process.env from config/shared-env-keys.ts, which the serve process.env guard does not scan — moving the access out of run-qwen-serve.ts dropped its allowlisted count and failed process-env-guard.test.ts. Pass the env into the coordinator instead so run-qwen-serve.ts still owns the process.env reference (matching the existing scrub helpers), and update the allowlist to the new count. * fix(cli): block GIT_SSH and GIT_CONFIG_PARAMETERS in the project-env denylist Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): extend the project-env denylist across git exec, TLS, and rc-file tiers Close the round-2 review findings: block the remaining git command-execution siblings (GIT_EXEC_PATH, GIT_TEMPLATE_DIR, GIT_ASKPASS, GIT_PROXY_COMMAND, GIT_EDITOR), the npm/pip TLS trust knobs (npm_config_cafile, npm_config_ca, npm_config_strict_ssl, PIP_CERT, GIT_SSL_CAPATH), and the curl/wget rc-file redirects (CURL_HOME, WGETRC) from project .env files. Freeze the numbered GIT_CONFIG_KEY_/VALUE_ pairs on reload together with GIT_CONFIG_COUNT, and sync the qwen-serve.md loader-key enumeration with settings.md. * fix(cli): harden the project-env denylist and nested scrub snapshot (#8763) * fix(cli): merge the loader-env scrub snapshot into one pass (#8763) acquireInheritedLoaderEnvScrub iterated process.env twice (a snapshot pass, then the scrub); record the originals inside the scrub's single pass instead. Drop the acquire-time snapshot clear, which the release-time clear made unreachable defense, and add tests that kill the previously surviving mutants on the release-time clear, the test-only reset, and the undefined-value guard. * fix(cli): block the round-4 exec-redirect env keys from project files (#8763) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
c0c0352e25
|
feat(cli): mirror Live Host releases through OSS (#8674)
* feat(cli): mirror Live Host releases through OSS * fix(cli): harden Live Host OSS mirror fallback |
||
|
|
358091833b
|
fix(ci): watchdog silent sandbox hangs and reap the containers they leak (#8816)
* feat(ci): A/B deterministic gate rejections against the pre-round ref A deterministic rejection in the autofix verification gate is only chargeable to the round if the same check passes without the round's commit. The gate charged every red to the fix unconditionally, and run 31276008548 measured what that costs when the premise is false: PR 8614's branch predated #8693's tsconfig guard while node_modules came from the post-#8693 trusted base, so `npm run build` was equally red at origin/<branch> — 63 minutes of accepted agent work discarded, an 18-minute repair burned on a failure the repair agent is forbidden to touch (it may only amend the round's own fix), thirteen rounds in a row, and the same again on the #8616 leg. On rejection the gate now re-runs the failing check at origin/<branch> (the branch as pushed, before the round) in the same environment: - baseline green: today's path exactly — outcome=failed, retryable=true, the repair pass gets its chance. - baseline red too: outcome=failed with preexisting=true and NO retryable. The repair step keys on retryable and is skipped — it cannot reach a failure outside the round's diff by construction — and gate-rejection.md says outright that the branch needs a base update (merge main), which flows into the failure comment as-is. Fail-closed toward today's semantics: any A/B infrastructure problem (missing ref, checkout failure) charges the fix as before, and a restore failure after the baseline run rejects outright since the tree can no longer be trusted. The round's work is still not pushed — this changes the verdict's honesty and cost, not the push policy. Tested by executing the real script in a real two-remote git repo with an npm stub whose failures are keyed by commit SHA: round-caused red (baseline green), pre-existing red (both red), and the untouched green path. Mutation-tested, 3 of 3 caught: skipping the A/B, claiming pre-existing without measuring, and dropping the tree restore. * Address review: bound the A/B to checks it can honestly compare All seven findings verified before fixing; the three Criticals were each a way the A/B compared something other than the check that failed. R1-1 — the contracts check feeds on stdin, which its first run drains; the baseline leg re-ran against EOF and checked an empty file list. R1-3 — the schema check's verdict rides on packages/core/dist, which the core-rebuild guard built from ROUND sources and which, being gitignored, survives the detach. Both checks are now A/B-exempt (run_check_no_ab): their baseline verdicts prove nothing, and their rejections stay where the repair agent can actually act on them. R1-2 — a workspace the round ADDS does not exist at the baseline, and npm exits 1 there with "No workspaces found" (measured; --if-present forgives a missing script, not a missing workspace) — a round-caused failure misread as pre-existing, skipping the one repair that can fix the round's own package. The per-package loop now A/Bs only when the workspace exists at origin/<branch>. R1-4 — a chatty PASSING baseline used to flood the tail -c 3000 evidence window and push the actual failure text out of gate-rejection.md, the sole carrier into the repair feedback, the PR comment, and the next round's LAST_REJECTION. The baseline transcript now goes to a side log and only a FAILING tail is merged back, where it is the evidence. R1-5 — the pre-existing paragraph pushed gate-rejection.md past the report's head -c 3500 cap, truncating the closing fence for branch names past 44 characters. Cap raised to 3900, invariant comment updated with the new arithmetic. R1-6 — preexisting=true had no read site. It now flows verify → Finalize verification → the failure report, whose headline swaps the generic gate clause for "PRE-EXISTING failure … needs a base update (merge main)". R1-7 — the no-round-commit guard was unpinned (deleting it kept all tests green). Now exercised through the core-rebuild path, the one A/B-eligible check that runs before the commit gate. Four new behavioral scenarios (chatty baseline, no-commit round, A/B-exempt checks, round-added workspace) plus workflow pins for the forwarding, the clause, and the cap. Mutation-tested, 4 of 4 caught: schema back to A/B (3 tests), guard dropped, side log reverted, no-commit guard dropped. * Address review round 2: A/B only what it can prove, prove what it claims Ten findings across two rounds, each verified before fixing. The three deepest share one lesson: the A/B is only sound for a check whose inputs travel entirely with the git ref, and whose failure it can IDENTIFY, not merely observe. R2-1 — rc=1 at both legs does not make them the same failure: the branch can fail for reason A while the round fails for reason B, and a baseline infrastructure hiccup is a nonzero exit too. Pre-existing now requires a MATCHING failure identity — tsc diagnostics normalized to file + error code (positions shift with the round's edits), compared via comm(1) on a per-check transcript. No diagnostics on either side means identity cannot be established and the round stays charged. R2-2 / R2-7 — gitignored dist survives the detach carrying the ROUND's build, so any dist-consuming check A/Bs reverted sources against round-built artifacts: package tests (channel-base resolved through dist exports) and typecheck (sdk-typescript resolves core's d.ts — probe-verified three-arm flip). Both are now A/B-exempt, as is lint, leaving `npm run build` — the incident class, and the one check that rebuilds its own inputs from the checked-out sources — as the sole A/B candidate. The workspace-existence guard dissolves with it. R2-3 — the fixture inherited the caller's global git config; a failing global pre-commit hook broke all seven cases. The harness now isolates GIT_CONFIG_GLOBAL/SYSTEM for every git child, and the suite is proven green under a deliberately hostile hooksPath. R2-4 — Finalize verification now selects preexisting from the same attempt whose outcome it selects (repair verification included). R2-5 / R2-8 — the "merge main" advice is now conditional at both layers: the script paragraph states the measured fact and hedges the remedy; the report headline uses the compare the step already ran — behind/diverged gets the base-update clause, an up-to-date branch is told its own pre-round code needs attention. R2-6 — the rejection document now sizes its evidence tail against its preamble (floor 500 bytes, total under the 3900-byte render cap), so the closing fence can no longer be truncated off by a long branch name. R2-9 — dissolved by R2-2: package tests no longer A/B, the guard and its uncovered positive branch are gone. R2-10 — the baseline-evidence merge is now pinned: the pre-existing scenario asserts the baseline leg's own failure line (keyed by its SHA) reaches gate-rejection.md. Eight behavioral scenarios; mutation-tested 5 of 5: identity dropped, typecheck re-enrolled, package tests re-enrolled, evidence merge dropped, fixed tail restored. * Address review round 4: sharpen identity, stage the git failures, sync prose Nine findings, all refinements — the design held, the edges did not. Identity now keeps the diagnostic MESSAGE (file + code collide: two unrelated TS2339s in one file compared equal, skipping a repair that could have shipped — probe-reproduced by the review), and the fixture emits a SHIFTED position on the baseline leg so the position strip is load-bearing instead of decorative (deleting the sed survived every test before; it fails one now). vite/esbuild failures still yield an empty signature by design — documented as the fail-closed limit rather than half-widened. The fail_signature assignments take `|| true`: grep exits 1 on the normal no-match case and survives errexit today only because the caller sits in an if-condition — a future unconditional call site would crash the gate verdict-less. The restore-failure branch is now stageable and staged: the baseline leg recreates (untracked) a file the branch tracks, the checkout back refuses, and the test pins retryable-not-preexisting with the 'could not restore' label. Relaxing the branch to `|| true` fails it. Prose synced to the mechanisms that replaced it: the render-cap invariant restates against the dynamic tail budget (the old 3000-based arithmetic would misguide the next retune), the no-round-commit guard comment names the core rebuild (schema/contracts left the A/B last round), the describe wording counts both A/B-eligible builds, and the pre-existing clauses no longer claim "the repair pass was skipped" — with REPAIR_PREEXISTING forwarded, repair may have RUN; they now state the invariant that is true either way: repair may only amend the round's own fix, so it cannot reach this failure. Mutation-tested, 3 of 3 caught: position strip dropped, message dropped from the identity, restore rejection relaxed. * fix(ci): watchdog silent sandbox hangs and reap the containers they leak Four autofix rounds have died the same way (#8663 twice, #8761 r3, #8763 r4): the agent's last output is the sandbox wrapper's "ContainerName (regular): …" line at docker container entry, then nothing — not one event — until the 2-hour absolute budget kills the round. Four different runners, two image versions: systemic, not a bad machine. Where exactly the container wedges is still unknown (that needs docker state on the runner); what is certain from the logs is the shape — a wedged sandbox produces NOTHING, and a legitimate run is never silent for long (the fleet's longest tolerated quiet is the review pipeline's 10-minute stream-idle window for thinking phases). Two mitigations, each aimed at a measured half of the damage: - run-agent.mjs gains an idle watchdog (QWEN_IDLE_TIMEOUT_MS, default 20 minutes = 2x that longest legitimate silence): zero output for the window kills the agent with a distinct "idle-timeout … the sandbox likely hung at startup" detail, so the failure comment names the right knob and a hung round costs 20 minutes instead of 120. Polled, not reset-per-chunk — a busy stream should not spend its time re-arming timers. - Both sandboxed jobs reap stale qwen-code-* containers at job start: a budget kill reaps the HOST-side docker client, not the container, so every killed sandbox keeps running on the persistent runner — observed directly when a later leg's container-name counter found qwen-code-0.21.8-0 already occupied and picked -1. One job per runner at a time makes any container alive at job start stale by definition. Tested by executing the real run-agent.mjs end to end with stub agents: the hang shape (one line, then silence) dies at the idle window naming the idle limit, and a slow-but-talking agent that outputs every 400ms across a 1500ms window survives to a clean exit — the test that distinguishes a watchdog from a disguised absolute timer. Mutation- tested, 3 of 3 caught: watchdog disabled, last-output tracking dropped (the disguised-timer regression), cleanup dropped from a job. * Address review round 5: the gate's verdict defects and the reaper's live kill Budget-warning round — the five Criticals from both reviewers, no suggestions (each deferred with a recorded reply). fail_signature: `[^\n]*` in an ERE bracket expression does not mean "rest of line" — in POSIX bracket expressions `\` is literal, so it matched "neither backslash nor the letter n" and truncated every tsc message at its first n. Nearly every real message has an early n ("Cannot find name", "is not assignable"), so distinct same-file failures collapsed into identical signatures and a round-caused failure could be labeled pre-existing, skipping the repair. grep is line-oriented: `.*` is exactly the rest of the line. New fixture: two messages differing only after their first n. Pre-existing verdict: the intersection test mislabeled in both directions. A round that ADDS a diagnostic sharing one normalized line with the baseline was called pre-existing (repair skipped for a round-caused, repairable failure); and `comm -12 | grep -q` under `set -eo pipefail` SIGPIPEs comm (exit 141) once the shared output outruns the pipe buffer, charging true pre-existing failures to the round — the exact 18-minute repair waste the gate exists to kill. Pre-existing now means the round's failing set is a SUBSET of the baseline's, and the difference is captured before testing. New fixture: a round adding a second diagnostic to a failing baseline. Restore failure after the baseline leg: was retryable=true with HEAD still detached at the baseline commit — the repair agent works in that very checkout and does no git recovery, so its commit would land on the baseline and be orphaned. Now rejected non-retryable (reject_fix grows a third arg); the next round starts clean from the trusted checkout. The restoreClash test pins the new semantics. Stale-container reap: the premise "a runner runs one job at a time, so any live qwen-code-* container is stale" holds per runner registration, but the filter queries the docker daemon, which is per host — and this pool runs several registrations on one OS. With per-issue/PR serialization only, a concurrent job's sandbox is a substring match away from `docker rm -f`. The reap now takes only provably-dead containers (--filter status=exited/dead, both jobs) and the comment says why a running one is left alone. Preamble printf: the `\`` escapes sat inside a single-quoted format where backslash is literal, so every pre-existing rejection rendered raw backticks instead of code spans (shellcheck SC2016). Backticks need no escaping there. Also syncs the side-log comment to the dynamic tail_budget it actually renders. Verified: scripts suite 140/140 (was 138; the two new fixtures and the rewritten restoreClash test all fail against the pre-fix script), npm run build / typecheck / lint pass, bash -n clean. * Address review round 6: reap the kill's own orphan, tolerate the reaper * Address review: hang-bound the reaper, unblock the kill path, pin the unpinned arms - Wrap every docker call in the stale-container reap with timeout 30: an alive-but-wedged daemon blocks docker ps indefinitely, and the existing || guards only catch nonzero exits, not hangs (R3-1). - Make the kill-path container removal async in run-agent.mjs: the spawnSync blocked the event loop between SIGTERM and the 10s SIGKILL backstop for up to its 30s timeout — in exactly the wedged-daemon scenario the watchdog exists for. The main flow awaits the removal so the leak warning stays deterministic (R3-6). - Split the pre-existing gate clause for an empty CMP_R: a transient compare-API failure is "never measured", not "measured not-behind", and must not assert the branch's own code is at fault (R3-7). - Swap the timeout breaker's closing remedy to the sandbox investigation when every counted timeout was idle, mirroring the round-level split (R3-11). - Tests: pin the budget kill path separately from the idle kill path (R3-3), parameterize the idle-window parse guard over -1/0/NaN (R3-5), add a stderr-only liveness case (R3-12), pin the strict-subset A/B arm via a baseline-superset fixture knob (R3-15), and pin the breaker's current-round idle increment (R3-18). --------- Co-authored-by: verify <verify@local> 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> |
||
|
|
8b056d8f81
|
chore(deps): sync external-context version in package-lock.json (#8858)
* chore(deps): sync external-context version in package-lock.json
The lockfile workspace entry still listed 0.21.8 while
integrations/external-context/package.json is at 0.20.1, so every
npm install rewrote the lockfile. Regenerated with
npm install --package-lock-only.
* fix(release): include external-context manifest in version commit
scripts/version.js bumps @qwen-code/external-context with every core
release and the follow-up `npm install --ignore-scripts` writes that
bump into package-lock.json, but the release version commit's `git add`
allowlist does not cover integrations/external-context/package.json.
The manifest bump is discarded while the lockfile bump is committed,
recreating the drift this PR cleans up after every release (seen in
v0.21.8, commit
|
||
|
|
77bd04bd61
|
fix(acp-bridge): bound live journal replay chunks (#8801)
* fix(acp-bridge): bound live journal replay chunks
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(core): isolate shell retention sidecars
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(integration): cover aggregated live journal replay
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(core): isolate registry sidecars
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(acp-bridge): keep unmodeled chunk keys out of live journal merges
The merged live-journal entry is rebuilt by spread-merging the first and
last source events, which was only safe because producers happen to emit
exactly {sessionUpdate, content, _meta?} on mergeable chunks. Gate the
merge on that key set so unmodeled data/update fields keep entries
discrete instead of leaking into the aggregate. Also clarify the
live-journal truncation marker: its retained/truncated counts describe
source events, while the limits count replay entries.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(acp-bridge): align replay boundaries for discrete and meta-shaped chunks
Turn compaction folded discrete thought chunks (and non-todo-stop-guard
discrete messages) into one text slot with the last chunk's meta, while
the live journal keeps every discrete chunk separate — resyncing from
compactedReplay mis-attributed text across background tasks. Guard both
chunk paths with the same hasDiscreteMessageMeta predicate the live
journal already uses. Also align the merge gate with the shapes the
shared meta builder emits: tolerate update-level timestamp/
serverTimestamp and qwenTranscript.planToolCallId, and treat an
empty-string parentToolCallId as top-level the way the extractor does.
Document that byte-cap truncation drops whole entries, so the retained
tail can be much smaller than the cap, and tighten the integration
assertion that became vacuous once entries merge source chunks.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(acp-bridge): merge subagent chunks in live journal replay
SubAgentTracker stamps every streamed subagent fragment with
{ parentToolCallId, subagentType }, but the live-journal merge gate
only modeled parentToolCallId, so subagent chunks stayed discrete and
a high-fragment subagent stream could still trip history_truncated.
Model subagentType as a carried label (like the completed-turn path,
which merges by parentToolCallId alone) and cover the producer wire
shape in the merge tests.
* fix(acp-bridge): preserve TextContent metadata in live journal replay (#8801)
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
|
||
|
|
0d56e50b64
|
fix(core): deflake the shell-registry fixtures, and share the display-strip helper (#8795)
Some checks are pending
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
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* test(core): give shell-registry fixtures their own output paths The retention-cap tests time out at 15s on shared CI runners while passing everywhere else, and it is not load. Every settle writes a status sidecar next to the entry's outputPath, and the fixture's default pointed all of them at one fixed path: `/tmp/s1.output`, so `/tmp/s1.status` on every machine that ever ran this suite. On a sticky-bit /tmp the file can already belong to another user, and then the atomic write's rename answers EPERM and retries with a blocking exponential backoff — 50, 100, 200ms — before giving up. That is ~350ms per settle, measured at 362ms; the cap tests settle 34 entries each, which is 12.3s of a 15s budget before the assertions even run. A/B on one machine with that exact shape (an unrenameable file planted at the shared path): the old fixture reports `Test timed out in 15000ms` three times over, the new one passes 57/57 in 778ms. The fix is the fixture, not the retry policy: the backoff protects real writes against transient EPERM and should stay. Entries now get output paths under a per-test temp directory keyed by shellId, which the existing afterEach already cleans, so no two entries — and no two jobs — share a sidecar. The two tests that hand-wrote `/tmp` paths to exercise `&` and control characters in a basename keep exactly that subject and move their directory too. Seen on feat/daemon-git-worktree-guard and feat/review-capture-tui, four tests apiece, same describe block, with the file taking 120s. * test(core): expect the notification path through production's own escape The rebuilt <output-file> expectations computed their value by hand — a single-occurrence String.replace, and none at all in the control-character test — while production emits escapeXml(stripDisplayControlChars(path)), which escapes all five XML metacharacters globally. So the expectations were only correct while os.tmpdir() itself contained none of & < > " '. That is the same "passes here, fails there" dependence this PR exists to remove, in a new form and introduced by its own first commit. Probe: under TMPDIR=/tmp/qcprobe/o&brien and .../o'brien, three tests fail with production behaving correctly. Routing every expectation through escapeXml makes all three environments agree — 57/57 under a plain TMPDIR and under both poisoned ones. Includes the pre-existing assertion in 'emits one task-notification when a shell completes', which carried the same latent dependence and now goes through the same pipeline. * test(core): compose the notification path instead of re-deriving it Third time on the same class, so this fixes the class rather than the case. Production renders <output-file> as escapeXml(stripDisplayControlChars(p)). My first pass hand-wrote the escape (single-occurrence, so only the first `&`), the second added escapeXml but not the strip, and each was correct only while os.tmpdir() happened to hold nothing the pipeline transforms — a TMPDIR under a control character, a C1 byte, or a second \x03 failed the assertion with production behaving correctly. The expectations now call the real functions in the real order through one helper, so they cannot drift from production at all. Verified 57/57 under a clean TMPDIR, one containing \x01, and one containing `&`; dropping either half of the composition turns three tests red under the matching hostile TMPDIR. (Noted while here, not changed: the registry keeps a private copy of stripDisplayControlChars byte-identical to the exported one in terminalSafe.ts. Out of scope for a deflake, but worth a look.) * fix(core): use the shared stripDisplayControlChars in the shell registry The registry kept a private stripDisplayControlChars that predates #4358: that change added the bidi override/isolate ranges (U+202A-U+202E, U+2066-U+2069) to the shared terminalSafe version and migrated monitorRegistry onto it, but left this copy behind. The copies have diverged ever since, even though terminalSafe's contract says both background notification surfaces must apply the same defense. The divergence bit this PR: the deflake's rebuilt <output-file> expectations compose escapeXml(stripDisplayControlChars(...)) from the shared utils — "exactly what production composes" was true for every input except bidi codepoints, one more pass-here-fail-there dependence in the class this PR exists to remove (and the earlier "byte-identical copies" note was wrong: the shared one strips two more ranges). Import the shared function and delete the stale copy. Notification rendering (cwd, command label, result, output-file) now strips bidi override/isolate characters — the documented Trojan-Source defense the shared function exists for — and the test's composition can no longer drift from production on any input. Net -20 lines. * test(core): pin escapeXml's GLOBAL replace, not just its five characters The composed oracle this PR introduced cannot discriminate a mutation in escapeXml — it computes the expectation with the same function under test, so both sides move together by construction. The literal case in xml.test.ts is where that property has to live, and it carried exactly one `&`: the other four metacharacters appeared twice and were pinned globally, `&` was not. Measured: mutating `.replace(/&/g, '&')` to `.replace('&', '&')` ships green across the whole package — 19,546 tests passed. A real path with two of them (a TMPDIR under `o&brien` with a basename like `out&err.log`, the very case this PR's own history is about) would then render a raw `&` into a model-facing XML envelope with nothing failing. The case now carries two `&`, and the mutant turns it red. * test(core): pin the bidi stripping the shared helper brought with it The swap to the shared stripDisplayControlChars is NOT behaviour-preserving, and I said it was. The shared helper carries two lines the registry's own copy did not — U+202A-202E and U+2066-2069 — so it strips bidi overrides as well as C0/C1, and that changes what five notification paths render. My "byte-identical" claim came from comparing the first dozen lines and stopping where they matched. Measured: `/tmp/a<RLO>evil<PDI>/out.log` renders unchanged through the old copy and as `/tmp/aevil/out.log` through the shared one. Those characters reorder how a path DISPLAYS without changing its bytes, so leaving them in a model-facing envelope is a spoofing surface; the stronger behaviour is the one to keep. Kept, then, but no longer undocumented: a registry-level test pins the bidi stripping in the notification, and reverting to a C0/C1-only helper turns it red. The PR title and body say what actually changes. * fix(core): strip bidi overrides from the output tail too The consolidation stopped one surface short, and my own test hid it: it asserts modelText-wide absence of U+202E, which reads as whole-envelope coverage, but its fixture shell has no output file — so <output-tail> renders the canned unreadable form and was never exercised. The tail is the LARGEST attacker-controllable field in the envelope, up to 8 KiB of a background shell's own output, and it renders through a different helper that stripped C0/C1 and passed bidi through verbatim. Probe-verified before the fix: a shell whose output contains U+202E and U+2069 puts both into <output-tail> unchanged. Fixed in that helper rather than by swapping in the shared one, because the tail must preserve newlines and carriage returns that the display helper strips. Pinned by a test whose shell actually writes an output file — the gap the previous pin had — asserting both that the overrides are gone and that the line structure survives. Reverting the two ranges turns it red. This is the Trojan-Source class (CVE-2021-42574): the characters reorder how surrounding text reads without changing a byte, which in a model-facing envelope means the model can be shown something other than what ran. * refactor(core): give the bidi range set one home and pin all nine codepoints * test(core): pin bidi stripping at the failed shell's <result> site * fix(core): strip bidi overrides from the monitor's streaming <result> (#8795) |
||
|
|
5855553428
|
fix(webui): Fence stale session work by attachment identity (#8833)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
feea80644e
|
docs(readme): add Korean to the documentation language bar (#8836)
The Korean docs went live with QwenLM/qwen-code-docs#212, so /ko/users/overview now resolves. Until now the language bar could not list Korean because that path 404ed. Uses the native name 한국어, matching the other entries, and appends it last so the existing order is untouched. |
||
|
|
9aec40f2d2
|
perf(review): cap the reverse audit and shed Agent 8 on a huge diff (#8773)
* perf(review): cap the reverse audit at one round below the sweep floor The sweep's rationale, extended to the high tier's second pass: below ~25 effective lines a diff fits in one view, and a second reader of the same few hunks is the same reader. The reverse audit is that second reader run as a LOOP — to two consecutive dry audits per chunk — and on a micro diff the loop re-reads the same lines round after round. Measured on a 23-line one-file PR: three rounds, eleven minutes, and the single finding round 2 produced was verifier-rejected. The plan's budget gains reverseAuditRounds (5, or 1 below the sweep floor; never 0 — one round IS the second look, the budget must not scale a dimension away). Under a cap of 1 a single substantive dry audit — or a retroactively-dry round — is the certificate, final, with no cold check: the retirement scheduler retires on one dry audit and reads retirement from round 2, so an all-dry round 1 exits 5 CONVERGED at the round-2 build. A hot chunk at the cap gets a deterministic ROUND CAP refusal from the admission gate (exit 4, the deadline gate's termination contract, naming the unreviewedDimensions entry only when scope is outstanding) — the cap is enforced by the builder, not by the orchestrator counting rounds. An older or garbled plan reads as the full cap: more auditing, never less. * perf(review): cap the reverse audit and shed Agent 8 on a huge diff Refocuses this PR from the micro-diff cap (which the review showed was mis-built: micro diffs run 3A, but the cap-1 machinery lived on the 3B path they never take, and its threshold never fired on the motivating case) onto the huge end, which is where the six-hour timeouts actually are. A timeout survey found 26 review-pr jobs dying in one recent window — ~122 hours of compute, zero posted, several the same PR retried. The wall clock is model inference (82-88% inside subagents, ~81% of that model turns), so on a 4,000-5,300-line PR the driver is sheer volume: dozens of agents reading the diff, then a reverse-audit loop whose every round re-reads it against a growing findings list (~90 min a round; five rounds alone exceed the ceiling). The elastic budget answers this in the band where the review otherwise posts nothing: reverseAuditRounds drops 5 -> 3 for a huge diff (effective >= 3000; three is the smallest loop two-consecutive-dry still converges), and specialistCap sheds Agent 8 to 0 there (a whole-diff pass on top of the base fan-out is the marginal cost that tips a too-big review over the wall). Neither drops a required dimension. The ROUND CAP refusal now writes a marker compose-review caps the verdict on, so a non-converged stop discloses like a budget stop rather than resting on the orchestrator's relay. Removing the cap-1 tier resolves the review's two Criticals and collapses the retirement scheduler back to its clean two-dry logic; the duplicated plan-cap read is gone (agent-prompt reads the parsed report, retirement no longer needs the cap at all). Tests pin the huge/normal boundary, the effective-vs-source split, round-past-cap enforcement at both 3 and 5, and the marker round-trip. * refactor(review): tidy round-cap prose left from the earlier iteration - Drop the always-plural ternary in the retirement note: dryRounds is a two-tuple again, so the singular branch is dead prose. - SKILL.md's merge bullet: the loop's bound is the plan's round cap (5, or 3 for a huge diff), not a flat 5-round cap. - Re-wrap the overlong deadline.ts header line. * fix(review): address reverse-audit round-cap review feedback - Floor `reverseAuditRoundCap` at the huge-diff cap of 3: an out-of-band 1 or 2 reads as the full cap, never fewer rounds. - Delete the dead `REVERSE_AUDIT_MAX_ROUNDS` re-export (no consumers) and point the retirement cold-check comment at the plan cap. - Correct the cap-3 rationale everywhere it was mechanically wrong: three is one audit round above the convergence floor of two (the all-dry rounds-1-and-2 shape converges under any cap of two or more, since the convergence check runs before the cap gate), not the smallest converging loop — in budget.ts, budget.test.ts, DESIGN.md and SKILL.md. - Define `effective` in the SKILL.md budget bullet and make the round-5 narration cap-agnostic; scope the retroactive-dry example to the cap-5 shape. - Add tests for the cap-3 retirement certificate, the per-chunk and chunkless round-cap gates, the undefined-round marker, and the specialistCap effective-vs-src dependence. --------- Co-authored-by: verify <verify@local> |
||
|
|
15f54145be
|
fix(desktop): restore the macOS window after closing it (#8802)
* fix(desktop): restore macOS window on reopen * fix(desktop): avoid stealing focus on reopen * fix(desktop): exit fullscreen before hiding window * fix(desktop): cancel pending hide on reopen * fix(desktop): cancel pending hide on refocus * test(desktop): cover should_restore_main_window truth table * fix(desktop): close fullscreen-hide races and tidy atomics - Run the pending hide on the main thread via run_on_main_thread so that a Dock/Finder reopen event serializes with the queued hide and cannot lose the race around the two-second fullscreen-exit boundary. - Clear the pending flag when set_fullscreen(false) fails so that a failed fullscreen exit does not leave the window hidden. - Use Release/Acquire/AcqRel ordering for the FULLSCREEN_HIDE_PENDING flag (cross-thread / cross-event-loop synchronization). - Gate FULLSCREEN_HIDE_PENDING and its focus_main_window store behind #[cfg(target_os = "macos")] so non-mac builds carry no dead state. * fix(desktop): isolate delayed fullscreen hides |
||
|
|
2e3d297eee
|
fix(cli): respect trusted env boundaries (#8643) (#8706)
Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local> |
||
|
|
e46586782c
|
feat: support drag and drop img in web-shell (#8696)
* feat(web-shell): support image drag and drop Allow Web Shell composers to ingest image files reliably while preserving the existing multimodal prompt protocol. - Share ordered image ingestion across desktop and mobile editors - Support image-only prompts and BMP preview and provider-safe handling - Preserve queued payloads across retries and uncertain outcomes - Add lifecycle guards, user feedback, unit coverage, and browser tests * fix(web-shell): harden image prompt admission recovery Preserve complete prompt payloads and prevent duplicate or uncertain delivery states when admission responses race with queue lifecycle events. - Correlate admission, queue, and terminal events by prompt ID - Restore images and input annotations across retry and edit flows - Bound image reader concurrency and encoded attachment memory - Reconcile confirmed removals and explain ambiguous queue entries * docs(web-shell): align image drag design with review fixes Document the reviewed admission, recovery, and resource invariants. Keep the design aligned with the hardened Web Shell implementation. - Record bounded image ingestion and encoded-data budgeting - Clarify prompt lifecycle correlation and confirmed removal behavior - Describe annotation restoration and internal action boundaries - Update focused validation evidence and acceptance criteria * fix(web-shell): avoid duplicate restored attachments Skip payload attachments when restoring text is a no-op because the same prompt text already exists in the composer. - Restore images and annotations only when their text is inserted - Preserve image-only restoration regardless of the current draft - Add regression coverage for duplicate text with attachments --------- Co-authored-by: heyang.why <heyang.why@alibaba-inc.com> |
||
|
|
57e6c31030
|
fix(web-shell): gate the floating todo entry behind the session workflow setting (#8828) | ||
|
|
e60d182241
|
perf(ci): tighten the automatic review kill switch for micro diffs (#8774)
* perf(ci): tighten the automatic review kill switch for micro diffs Below the review skill's sweep floor (25 changed lines) an automatic review keeps --effort high and its inline comments — a medium downgrade would drop exactly the inline findings a source fix deserves, and with the file-scoped suites and the one-round reverse audit the pipeline itself is what shrinks on a micro diff. What a micro run must not keep is a multi-hour kill switch: the small-PR budget halves with the same 90-minute floor the docs-only downgrade uses, so a hung run dies at the scale of its work. Reuses the PR_SIZE_LINES the size-aware budget already fetched — no new API call; an unknown size never tightens, an explicit --timeout wins (the size block is skipped), and a docs-only run is already halved, never twice. Gate tests drive the extracted step source with seeded sizes: the 24/25 boundary, the floor, the docs-only interaction, and the unknown-size fallback. * fix(ci): share one halve-with-floor implementation and pin the guard states Review rework. The docs-only branch and the micro tightening now call one halve_budget_floor() — a / 2 → / 3 mutant survived every test because both micro inputs land on the floor under any divisor ≥ 2, and two verbatim copies let a one-sided edit diverge the branches while the comments claimed they matched; the floor cases now execute the shared function and a structural pin asserts one definition, two calls, one occurrence of the arithmetic. The threshold comment states the unit honestly (total churn rides the skill's source-weighted sweep floor in the direction that cannot over-tighten). Two surviving guard mutants get pins: a manually requested review with a populated size is never tightened (the caller owns its timeout), and a failed docs classification still tightens a micro automatic run (the guard keys on != "true", not = "false"). * fix(ci): state the micro-tightening's true justification, not a false invariant The comment claimed "total churn < 25 implies the skill's source-weighted measure < 25", which is backwards: srcDiffLines counts raw unified-diff lines (file/hunk headers, context), so it is LARGER than churn — a scattered micro diff (churn 10 across 5 files) computes srcDiffLines ~65 and keeps sweep on, un-shrunk, while the gate still halves its budget. The threshold is now stated as what it is: an independent "small PR" churn bound, deliberately not SWEEP_FLOOR (the two measures differ, so a micro diff may still run the sweep and the full reverse audit). The tightening is justified by "churn < 25 bounds the reviewed territory and 90 minutes is ample for it even on the full pipeline" — measured, a 23-line PR runs high end to end in ~30 min — not by the pipeline shrinking. With the SWEEP_FLOOR coupling claim removed, there is no cross-file coupling left to drift, so no equality pin is owed. * fix(ci): reword the micro test comment to the independent churn bound Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): reword the micro gate comment to the 180-minute budget it actually halves Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
27216eb4ca
|
feat(web-shell): show context usage as a mini progress pill in the status bar (#8794)
* feat(web-shell): show context usage as a mini progress pill in the status bar Replace the plain "X% context used" text in the status bar with a compact pill: a 52px progress bar plus the bare percentage. The fill follows the same thresholds as the /context panel (>60% warning, >80% error), the fill width caps at 100% while the number keeps reporting overflow, and the full wording moves to aria-label so the accessible name is unchanged. Clicking still opens the /context breakdown. * fix(sdk): stop rendering usage_update frames as debug transcript text The ui normalizer had no case for the usage_update session update, so the frame fell through to the debug default and every model round appended a raw-JSON bullet to the assistant turn in the web UI. Context occupancy is surfaced by the status bar pill; the transcript drops the frame like current_mode_update. * feat(web-shell): move the context indicator into the composer toolbar as a ring Review rework: replace the status-bar pill with a compact circular progress ring in the composer toolbar's right cluster, immediately left of the voice actions. The ring keeps the /context thresholds (>60% warning, >80% error) and the visual 100% cap, hovers a Tooltip with the full used/total detail (e.g. 53.6k / 1.0M tokens (5.4%)), keeps the full wording on aria-label, and still opens /context on click. It ships as a new contextUsage entry in composerToolbarActions so embedders can hide it, hides while usage or the window is unknown, and follows the toolbar's mobile-voice hiding. The StatusBar changes are reverted so the indicator lives in exactly one place. * fix(web-shell): let Radix position the shared tooltip arrow The shared TooltipContent drew its arrow with a ::before pinned at the content's horizontal center. Near a viewport edge Radix collision avoidance shifts the content, so the arrow stopped pointing at the trigger (about 35-40px off for the composer's context ring, which sits at the far right). Replace the pseudo-element with TooltipPrimitive.Arrow, which computes the offset from the trigger and the collision-shifted content, keeping the tip on target for every tooltip and side. * refactor(web-shell): share context-usage thresholds and token formatting (review) Review round 3 suggestions: - The 60/80 severity thresholds now live in one shared helper used by both the composer ring and the /context panel, so the two surfaces cannot drift. - The ring tooltip's token formatter moves into the shared token-count utils and the /context panel uses it too, giving both surfaces the same k/M rendering (the panel previously showed a 1M window as 1000.0k). - The ring arc's transition is disabled under prefers-reduced-motion, matching the file's other decorative motion. - New tests: exactly-80% stays warning (pins the strict threshold), the App wiring from connection usage to the ring props, click-through reaching the context-usage request, and the 0 fallbacks before any usage arrives. * fix(sdk): resolve usage_update overlap with main The same normalizer fix landed on main via #8790 while this PR was in review; the merge auto-combined both edits into a duplicate case and a duplicate test. Keep main's version — this branch now carries no sdk-typescript delta. * refactor(web-shell): consolidate the remaining token-count formatter copies (review) Review round 4: the task-status panel's local formatter was byte-identical to the shared one, and the collapsed-turn footer's copy lacked the M branch — a collapsed turn with a >=1M-token input rendered 1048.6k while the ring tooltip and /context panel said 1.0M for the same session. Both now import the shared formatter. Also pin the tooltip arrow's positioning classes in the test, so a shadcn regeneration that drops them fails instead of passing on a bare existence check. * fix(web-shell): restore tooltip spacing and finish formatter consolidation (review) Review round 5: - With a Radix Arrow child, the offset middleware computes sideOffset + arrowHeight, so keeping the pseudo-element-era default of 8 pushed every tooltip ~10px farther from its trigger. Default now 0; measured in a real browser the content edge sits 10px from the trigger (8px before the arrow change) with the tip 6px away. - Drop the formatContextTokens-as-formatTokenCount aliases: the alias reused the exact name of the module's other, differently-behaving export, inviting wrong-import drift. Call sites use the real name. - Colocate the pure-logic tests: utils/contextUsage.test.ts pins the strict-> boundaries, utils/formatTokenCount.test.ts owns the formatter cases (moved from ChatEditor.test.tsx), and ContextUsageMessage gains progress-bar color cases at 60/61/80/81 so the panel half of the shared-threshold contract is pinned too. |
||
|
|
856b793c15
|
fix(desktop): surface automatic update failures (#8807)
* fix(desktop): report updater installation failures * fix(desktop): avoid unsafe updater retries |
||
|
|
af372e5a21
|
perf(review): guarantee compose survives a reverse-audit budget stop (#8791)
* perf(review): guarantee compose survives a reverse-audit budget stop PR #8687 — a 4,269-line cross-worktree git guard — timed out after six hours and posted nothing, holding ~20 E2E-confirmed Critical bypasses. The deadline gate worked: it refused round 3 correctly with ~110 minutes and the whole reserve in hand. The tail after the stop was the killer — a single hand-rolled verification agent re-running a 15-family shell/git bypass battery with real filesystem E2E consumed all of it, and the wall hit mid-verification before compose-review ever ran. The reserve was one number covering "verification + compose + submit", which is right for a normal per-finding re-trace but wrong for a security PR where verification cost is unbounded (real E2E per finding) while compose and submit stay bounded. So a distinct, smaller compose FLOOR is carved out and the VERIFIER — not the reverse-audit builder — is gated on it: below the floor `agent-prompt --role verify` refuses to build (VERIFY BUDGET, exit 4), the findings keep their `— [unverified]` tag for compose-review to cap, and compose runs. The floor is strictly below the reserve, so a healthy run reaches the reverse-audit gate first and never sees it; it is the cover for the one span the reserve cannot bound. The prose closes the bypass the gate cannot see: the post-stop tail verifies only through the gated builder, never a hand-rolled agent, and invents no fresh re-verification pass for findings already confirmed — compose and submit are non-negotiable. DESIGN.md records the incident; the RA budget message and SKILL Step 5 tail are rewritten to match. * fix(review): close the round-1 gaps in the compose-floor gate - R2-2 (Critical): the documented `0` escape hatch did not disable the verify gate past the deadline — `remainingSeconds` goes negative there and `negative >= 0` is false, firing the supposedly-disabled gate. verifyBudgetExhausted now returns null the moment the effective floor is 0, before the comparison. Pinned with a past-deadline case. - R2-1 (Critical): the gate bounds prompt CONSTRUCTION, not the wall time of an already-admitted verifier that then runs a long E2E past the floor — and agent-prompt builds prompts, it cannot cancel a running agent. The SKILL tail now tells the orchestrator to bound the WAIT: when the deadline is within the compose floor and a verifier batch has not returned, stop waiting on it, keep its findings unverified, and compose. The remaining execution-time cancellation is a harness capability, noted as such (same layer boundary as the hand-rolled-agent caveat). - R2-3: the agent-prompt exit-code help now documents both the BUDGET and VERIFY BUDGET exit-4 refusals. - R2-4: the reverseAuditBudgetMessage test now pins the new tail rules (gated verifier only, no hand-rolled agent, no re-verification). - R2-5: docs/users/features/code-review.md documents the compose floor — default, env var, reserve nesting, exit-4 behaviour, zero hatch. * fix(review): round-2 fixes for the compose-floor gate - R3-1 (Critical): the verify gate admitted at exactly the floor, where the first work crosses below it — the floor is compose-only with no margin, so it now refuses at equality (`> floor`, unlike the RA reserve which admits at exact cover). Exact-boundary test flipped. - R3-2 (Critical): the refusal message and SKILL claimed unverified findings "post as needing human review", but the confirmed-only rule keeps tagged details terminal-only. Reworded to the true contract: compose-review caps the verdict and discloses the verification gap; the tagged details stay terminal-only; what posts is the earlier rounds' confirmed findings plus that gap. - R3-5: extracted readDeadlineSeconds / readNonNegativeSeconds, shared by both gates so the fail-open contract lives in one place. - R3-3: pinned the verify gate's fail-open branches (malformed/non-positive deadline, past-deadline negative remaining, negative-floor fallback). - R3-4: pinned the floor-minutes rendering (a field swap to remainingSeconds would misstate the protected floor). - R3-7: pinned that a refused verifier writes no budget-stop marker and no admission stamp. - R3-8: pinned validation-before-gate (a malformed verify call under the floor throws, not exit 4). R3-6 needs no change: the SKILL.test pointer<->heading gate already covers the DESIGN section (a dangling pointer fails it). * fix(review): round-3 cheap fixes for the compose-floor gate Low-risk corrections; the two edge-case Criticals (R4-1 broken-plan masking, shared with the RA gate; R4-2 compose-review relaunch FIX) are left as follow-ups — noted on the threads. - R4-4: the readDeadlineSeconds extraction stranded reverseAuditBudgetExhausted's contract JSDoc above the helper; moved it back onto the function. - R4-5: the round-2 "terminal-only, never posted" wording contradicted compose-review's own verdict line ("posted, disclosed as unverified") — a pre-existing contract ambiguity this PR should not relitigate. Reworded the message and SKILL to the invariant both readings share: an unverified finding is never treated as a confirmed blocker; the verdict is capped. - R4-7: "below the N-minute floor" contradicted the exact-equality refusal (the gate admits on `> floor`); now "at or below", in the message and the user docs. - R4-3: pinned that a blank/whitespace floor override falls back to the default (only explicit 0 disables). - R4-6: pinned the negative-remaining clamp in verifyBudgetMessage. --------- Co-authored-by: verify <verify@local> |
||
|
|
cc46babf79
|
feat(desktop): create default workspace on first launch (#8814)
* feat(desktop): create default workspace on first launch * test(desktop): cover default workspace failure * feat(desktop): honor QWEN_DEFAULT_WORKSPACE_DIR for the default workspace * fix(desktop): defer default workspace creation; kill in-flight runtime on stop Move default-workspace directory creation out of setup into the runtime start blocking task, so the first touch of ~/Documents (which can raise the macOS TCC prompt) no longer blocks the main thread. Path resolution (initial_workspace/default_workspace) is now pure and reports whether the derived default directory must be created; creation failures surface through the existing runtime-failed path. Register the spawned runtime child in a shared pending handle before the startup wait, so app exit, restart, and generation switches can kill an in-flight daemon instead of orphaning it in its own process group. The startup wait loops poll the shared handle and treat a taken child as a stop during startup. * fix(desktop): close runtime startup cancellation race * fix(desktop): preserve default workspace retries |
||
|
|
916a8d97aa
|
fix(desktop): open Local Control on the active session (#8806)
* fix(desktop): share the active session safely * fix(desktop): reject malformed Local Control headers * test(desktop): cover Local Control boundaries * test(desktop): cover bare-CR header and empty workspace guard |
||
|
|
33e602cc41
|
fix(test): stop background-shell tests sharing a fixed /tmp sidecar path (#8813)
* fix(test): stop background-shell tests sharing a fixed /tmp sidecar path
`makeEntry` defaulted to `outputPath: '/tmp/s1.output'`, so every entry in
this file — across tests, across workers, across CI jobs on the same host —
mirrored its status sidecar to the single path `/tmp/s1.status`.
`/tmp` carries the sticky bit. Once that file belongs to another uid, the
atomic rename in `atomicWriteFileSync` fails EPERM, and
`renameWithRetrySync` burns its full 50+100+200ms backoff before the
registry swallows the error. Every register/complete then costs ~350ms and
the sidecar never lands.
That is what the loop tests were paying: the retention-cap cases do 68
register/complete calls, and CI measured 23.8s each. The durations across
the whole file were exact multiples of 351ms — 352 / 703 / 1405 / 2113 /
3520 — with no variance, which is the backoff sum, not disk latency.
Give each entry its own temp directory instead. The shared path is gone,
the rename succeeds, and the file drops from 128.9s to 3.2s locally with
`/tmp/s1.status` made immutable to reproduce the CI condition.
#8797 raised these four cases to a 120s timeout to survive the cost. With
the cost removed the band-aid goes too, so a future regression fails loudly
instead of silently taking two minutes.
* fix(test): unify sidecar test helpers and pin per-entry outputPath uniqueness
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test: assert the whole output-file element, not just its tail
Unifying the sidecar helpers moved these two entries onto random temp
directories, and the assertions were relaxed to a suffix match to cope.
Rebuild the expected element from the path under test instead: the temp
prefix is random, but the escaping and control-byte stripping these two
cases exist to pin are exact.
* test: escape expected XML paths the way the registry does
The anchored `<output-file>` assertions built their expected value by hand —
one replaced `&` only, the other nothing at all. `tmpdir()` may legally
contain XML metacharacters (`&` on Windows, `<` on POSIX), so those cases
became environment-dependent the moment they moved off the fixed `/tmp`
path.
Run the expected path through the same `escapeXml(stripDisplayControlChars())`
the registry uses. Verified with `TMPDIR=/tmp/qwen-xml-probe/a&b<c`: the
helper passes all three focused cases, while the hand-rolled version fails
two.
* test: escape last dynamic output-file expectation, stop sidecar leak
The escape conversion in
|
||
|
|
b314d01f2d
|
fix(web-shell): stop rendering unrecognized daemon events in transcripts (#8812)
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(web-shell): stop rendering unrecognized daemon events in transcripts
The daemon UI normalizer projects any frame it has no case for into a
`debug` event carrying a raw JSON dump. webui's ChatViewer drops those
blocks, but Web Shell renders `status` and `debug` together as system
info, so every event kind the daemon ships ahead of the UI surfaces as
unreadable JSON in the middle of the conversation. This has been patched
per-symptom three times now: two string-prefix suppressions inside
`isIgnoredWebShellStatus`, plus #8790 for `usage_update`.
Give the normalizer's debug events a structured `debugReason` and let
Web Shell branch on it instead of pattern-matching text:
- `unrecognized_event` / `unrecognized_session_update` — the daemon runs
ahead of this client; developer diagnostics, not conversation content.
Web Shell no longer renders them.
- `malformed_payload` — a frame the client does know arrived unusable.
That is a real defect signal, so it stays visible.
Debug events dispatched by clients themselves, such as Web Shell's own
model-switch summary, carry no `debugReason` and keep rendering.
The two `(unrecognized daemon event)` prefix checks are now covered by
`debugReason` and are removed; the `Model switched: ` check stays, since
`model.changed` projects to a `status` block rather than a debug one.
* fix(sdk): classify a discriminator-less session_update as malformed
Review of #8812 caught a hole in the new classification: `session_update`
payloads such as `{}` or `{ sessionUpdate: 42 }` reach the default branch
with `kind === undefined`, and stamping them `unrecognized_session_update`
made Web Shell hide the only diagnostic a malformed frame produces.
Reserve the unrecognized reason for a real unknown string kind.
Also update the top-level default-case comment, which still pointed
adapters at the debug text prefix, and add a reducer-level test proving
`debugReason` survives the UI-event → transcript-block boundary: the
normalizer tests inspect events and the Web Shell tests build blocks by
hand, so dropping the spread in transcript.ts would leave both green.
* fix(web-shell): keep filtering legacy debug blocks, tighten the reason split
Four review findings from #8812:
- `WebShellTranscript` is a public entry point taking already-projected
blocks, so blocks from an SDK predating `debugReason` still arrive with
no reason and started rendering again when the prefix checks were
removed. Fall back to the stable ` (unrecognized daemon event): ` marker
when no reason is present — which covers every unrecognized event type,
not just the two previously suppressed by name. The old-shape fixture is
restored (adding `debugReason` to it had hidden this path) and a
dedicated legacy test now pins it.
- A whitespace-only discriminator is truthy, so `sessionUpdate: ' '` was
classified unrecognized and hidden. Gate on `trim()`, matching the
convention `getFirstString` already uses.
- Add the mirror invariant for the reducer: a client-dispatched debug
event must produce a block with no `debugReason`. Defaulting the field
in `appendStatusBlock` otherwise passes every other test while tagging
the model-switch summary unrecognized.
- Guard the outermost public re-export. A type-only guard would not hold —
vitest erases `export type` through esbuild and this package's tsconfig
excludes `test/` — so ship the union as `DAEMON_UI_DEBUG_REASONS`,
matching `DAEMON_ERROR_KINDS`, and assert it at runtime.
* fix(web-shell): suppress legacy usage_update/a2ui blocks with no debugReason
Follow-up verification on #8812 pointed out the marker fallback does not
close the original report. #8790 stopped the SDK inserting new
`usage_update` blocks, but `WebShellTranscript` renders whatever blocks its
caller passes, so a transcript persisted or projected before that still
holds them and the spam returns after upgrade.
The legacy `session_update` projection is `<kind>: <json>` with no marker to
key on, so match those by kind name instead. The list is closed on purpose —
`usage_update` and `a2ui`, the two known to have leaked — and requires the
`: {` shape, because a generic `<word>: {` rule would swallow legitimate
diagnostics. Blocks the normalizer classified still win on `debugReason`,
so `malformed_payload` and client-dispatched debug blocks stay visible.
Mutation-checked in both directions: dropping the fallback fails the legacy
test, and loosening the prefix to bare `usage_update:` fails the test that
pins prose and classified blocks staying visible.
* fix(web-shell): match the legacy projection shape, not a quoted marker
The legacy fallback was too broad in two ways, both reachable. It ran for
`status` blocks as well as `debug` ones, because this helper is called from
the shared `case 'status': case 'debug':` arm, and it matched the marker as
a substring anywhere in the text.
Probed at
|
||
|
|
55e20db328
|
feat(workflows): add an orchestration policy layer to the Workflow tool description (#8694)
* feat(workflows): add an orchestration policy layer to the Workflow tool description The description was an accurate technical specification that said nothing about when to orchestrate, which shape to use, or how to trust what comes back. With only the API in view, the naive shape wins every time: fan everything out through one barrier and take the first answer at face value. Adds the decision layer on top of the existing runtime facts — purpose framing, pipeline-by-default with an explicit test for when a barrier is genuinely required, scout-then-orchestrate, reusable shapes, adversarial and perspective-diverse verification, the deduplicate-against-seen rule that keeps discovery loops terminating, and honest reporting of bounded coverage. Prompt text only: no runtime, sandbox, or schema change. Closes #8690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(workflows): close the description's limit gaps and pin every policy section Addresses the round-1 review on #8694 (R1-1 through R1-6). The description now names the three limits a model has to plan around rather than discover from a mid-run failure: `workflow()` nests one level only, the 30-minute wall-clock cap per run (`QWEN_CODE_MAX_WORKFLOW_SECONDS`), and the per-run output-token cap — surfaced as a `budget.total` probe rather than a knob name, keeping the P5 R2 rule that the env var stays out of model-reachable text. The pinning test grew anchors for the three policy sections a mutation probe could delete while it stayed green, and for `QWEN_CODE_MAX_WORKFLOW_CONCURRENCY`, which the description advertised with nothing asserting its spelling. The JSDoc claim that the authoring contract "is not duplicated here" was false — the limits and env knobs appear in both halves — so it now says they are a summary to keep in sync. * docs(workflows): anchor the planning caps and pair each env knob with its limit Round-2 review (R2-1/R2-2/R2-3) plus the two non-blocking suggestions from the maintainer verification round. - R2-1: anchor the two env knobs through the orchestrator's exported MAX_WORKFLOW_AGENTS_ENV / MAX_WORKFLOW_CONCURRENCY_ENV instead of hardcoded literals, so a rename on the runtime side fails the guard too. QWEN_CODE_MAX_WORKFLOW_SECONDS has no exported constant (workflow-sandbox.ts reads it inline), so it stays a literal. - R2-2: pin the numbers the model plans around — "up to 1000 agents total" and the "30-minute wall-clock cap" — not just the knob names. - R2-3: the sync note claimed the whole limits block restates the `script` contract; the wall-clock cap, token budget and one-level workflow() nesting limit live only in the tool description. Say which is which. - W-1: budget exhaustion refuses each further agent() call; a bare sequential await sees the rejection, while parallel()/pipeline() turn the refused slot into null and keep running (errors-as-data, settleToNullArray). - W-2: pair each env knob with the quantity it overrides — the shared parenthetical read as if both knobs covered the total. Mutation-probed: 1000->500, 30-minute->15-minute, and renaming MAX_WORKFLOW_AGENTS_ENV's value each turn the suite red on the intended assertion; restored tree is 40/40 green. * fix(workflows): derive description caps from the runtime constants Addresses the four Suggestions from review round 3 on #8694. R3-2 / R3-3: the agent cap and the two env-knob names were prose literals in both model-visible halves — the tool description and the `script` parameter description — a third copy sitting in the pinning test. Interpolate `DEFAULT_MAX_AGENTS_PER_RUN`, `MAX_WORKFLOW_AGENTS_ENV` and `MAX_WORKFLOW_CONCURRENCY_ENV` from `workflow-orchestrator.ts` into both halves instead, so raising a cap moves every copy at once and the model can no longer read two contradictory caps from one tool call. `DEFAULT_MAX_WALL_CLOCK_MS` stays a literal: it is private to `workflow-sandbox.ts`. The doc comment now says which values still need hand-syncing rather than claiming all of them do. R3-1: the numeric anchor pinned the description's own literal, so it could not catch the drift its comment claimed to catch. Anchor it through the exported constant, and add a test pinning the `script` parameter description's copy of the same caps — nothing asserted it before, so a maintainer could raise a cap, get the tool-description test green again, and leave `script` advertising the old number. R3-4: issue #8690 asked the text to use this project's vocabulary. The description advertised `workflow('<name>')` without ever saying where saved workflows live, leaving the model to guess a name blindly or construct an absolute `scriptPath` it has no way to know. Name both scopes and their precedence. Verification: npm run build, npm run typecheck, eslint on both changed files, and vitest on workflow.test.ts + workflow-orchestrator.test.ts (174 passed). Probed the new anchors both ways with the cap temporarily raised to 2000: the suite stays green (the description tracks the constant), and pasting the literal `1000` back into the description turns both assertions red. --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0a3d7bb5c1
|
feat(acp): Protect against repeated tool execution failures (#8469)
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 / 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
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
* feat(acp): protect repeated tool execution failures Add a conservative prompt-local guard for repeated typed ACP tool execution failures, with shadow/warn/enforce rollout modes, privacy-safe telemetry, and coverage for the final execution outcome contract. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): harden repeated tool failure guard rollout Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): address repeated failure guard review Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): improve repeated failure guard recall Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * docs(acp): clarify review and rollout gates --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
a810f7e16c
|
fix(serve): Make session restore timeouts safe and observable (#8691)
* fix(serve): make session restore timeouts safe Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): restore missing core mock exports in the ACP worktree suite The restore-tracing change added `extractDaemonTraceContext` and `withDaemonSpan` to `acpAgent.ts`, but `acpAgent.worktree.test.ts` replaces `@qwen-code/qwen-code-core` with a full mock factory that never listed them. `loadSession` then failed on an undefined export, taking all three cases down and producing teardown rejections from the half-built agent. The sibling suite was updated; this one was missed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): bound and disambiguate the abandoned restore lifecycle Four follow-ups from review of the restore timeout work. A startup budget may now raise the restore budget but never lower it. Taking an explicitly configured `initializeTimeoutMs` as the restore fallback meant a deployment that tightened its child-initialize check still inherited a sub-default restore deadline — exactly the failure this change exists to remove. An explicit `sessionRestoreTimeoutMs` still wins outright, including below the default, for deployments that want restore to fail fast. Validation now names the field actually at fault. A restore fenced behind a timed-out predecessor is no longer reported as an ordinary in-flight restore. It carries `reason: awaiting_abandoned_cleanup` and a retry hint of one restore budget (capped at 120s) instead of the ordinary 5 seconds, because the fence cannot clear until the non-cancellable ACP request settles and a 5-second cadence just spins the caller against a 409 it cannot resolve. Whether a channel is condemned is now derived rather than sticky. A timeout recorded `emptyReapPending` permanently, so any channel that had ever seen one was guaranteed to be reaped once its remaining work drained, forcing a cold respawn even when the late restore had landed and closed cleanly. The reap condition is now computed from an outstanding `unsettledAbandonedRestores` set, quarantine, or an ordinary pending empty reap; real settlement clears the entry and hands the channel back to the configured idle policy. Abandonment no longer retains ownership without bound. One further restore budget after the deadline, a still-unsettled restore marks the channel `restoreSettlementOverdue`: existing sessions and workspace control keep working, but fresh session work is refused so the channel can drain, since closing the transport is the only lever that releases a permanently hung request. Releasing capacity while hidden work runs would allow unbounded oversubscription, and force-killing a channel with live siblings would reintroduce the failure this work removes, so neither is done. Fresh-admission blocking is now scanned across alive channels rather than tracked in a single reference, so a second condemned channel cannot silently displace the first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): keep the abandoned restore lifecycle off ids it no longer owns Two correctness gaps in the abandoned-restore machinery introduced by this PR, both reported by automated review and both confirmed by mutation testing (each new test fails when its fix is reverted). A caller-supplied `sessionId` is used verbatim by the agent, but `spawnOrAttach` never consulted `inFlightRestores`. A fresh spawn could therefore take an id that a restore still owns, in either lifecycle phase. The consequences were silent: `abandonedRestoreIds` suppresses session updates, guardrail events, and child notifications, so the new session would have registered successfully and then emitted nothing; and a late `settleAbandonedRestore` would have closed and tombstoned it out from under its owner. Such a spawn is now rejected with the same `RestoreInProgressError` and reason the restore path uses, so the caller gets the correct retry hint for whichever phase is holding the id. The cleanup path is guarded independently, because the request-level check only covers the id the caller asked for and a session registers under the id the child returns. An abandoned restore never reaches `createSessionEntry` — the deadline rejects before registration — so any live entry under that id belongs to someone else. Cleanup now detects that and returns without closing or tombstoning, releasing its own bookkeeping instead. The notification fence has no TTL and was only cleared by `markRestoreInFlight`, which covers a subsequent restore and nothing else. `createSessionEntry` now clears it for every registration route, so a legitimate owner of the id is never handed a session that silently drops everything the child sends it. Also tightens two tests that could not observe the values they pin. The SDK default restore timeout admitted any value in (30s, 70s]; it is now split at the exact boundary, so collapsing the default onto the 60s server budget — which would make the client abort race the daemon's own deadline and cost the caller its structured 504 — fails. And the advertised-budget propagation from capabilities through to the SDK call had no live-path assertion; dropping the capabilities argument at the real call site left every existing test green. The `as never` casts are replaced with typed `DaemonCapabilities` values so a field rename fails typecheck. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): let a condemned channel drain without its wedged child Merging main's active-work close protocol (#8588) into this PR's abandoned restore bound produced a deadlock that neither side has on its own, and the conflict resolution was committed without running tests. `maybeCloseIdleSession` now routes through `confirmChildUnheld`, which asks the child whether it still holds work before closing a session nobody is attached to. That is right in general and wrong for a channel this PR has already condemned. `restoreSettlementOverdue` and quarantine exist precisely because the child stopped being answerable, and their whole premise is that visible work drains so the channel can be reaped — closing the transport is the only thing that can release a restore we cannot cancel. Making that drain depend on a round trip to the wedged child inverts it: a child stuck in a non-cancellable restore is exactly the one that cannot reply inside `ACTIVE_WORK_CLOSE_TIMEOUT_MS`, so the sessions never close, the channel never drains, the reap never fires, and the bound never takes effect. A channel condemned by the restore lifecycle now skips the round trip and proceeds to local teardown. Nothing is attached to the session by then — `maybeCloseIdleSession` gates on that — and the sibling-safety invariant is untouched: this closes sessions whose clients have already left, it does not force-kill a channel that still has live ones. The regression test drives an overdue channel whose child never answers the close-if-unheld probe and asserts the detach still reaps it. Reverting the guard reproduces the deadlock as a test timeout. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(serve): pin the restore-timeout contract the review found unasserted Automated review identified eleven places where the restore-timeout work's behavior was correct but unpinned — each with a mutation that ships green. Every fix below was verified the same way: apply the mutation, watch the new assertion fail, revert, watch it pass. The timeout path's telemetry had no coverage at all, which is the sharpest gap given that observability is what this work exists to deliver. A shared recorder now asserts the public timeout result and its kill_empty-vs- fence_shared signal, the late arrival, and the cleanup outcome for both the closed and quarantined cases. The deadline timer's cancellation on a successful restore was likewise unpinned: deleting both `clearTimeout` calls kept the whole suite green, while in production the stale timer fires one budget after a successful restore and abandons a live session — fencing its frames, closing its event bus, and emitting a spurious timeout. A success-path test now advances past the deadline and asserts no second public result. Three more bridge assertions proved less than they claimed: the concurrent- restore case never checked that the abandoned restore settles, the workspace-control case never checked that the deferred reap eventually fires, and the resolver never pinned the accepting side of the MAX boundary (a `>` to `>=` mutation rejects the largest legal delay at boot). The workspace-control case also needed a positive channel idle budget, since with the default zero the idle-timer kill substitutes for the reap junction under test; its assertions are rewritten around the derived reap semantics rather than the sticky flag they predate. Outside the bridge: the scheduled-task timeout wiring had no test, so deleting the arguments silently fell back to the helpers' own defaults; the cold restore path never asserted that `live_restore_ms` is absent; the SDK's per-request validation and its over-ceiling clamp were untested; the WebUI watchdog test jumped straight to its own value, staying green for any watchdog at or below it, including the 30s attach value that would recreate the original symptom in the browser; and the two new known error types were unexercised, so dropping either would relabel every restore-timeout and quarantine error as unknown. Two review items are deliberately not taken here and are recorded in the design doc's non-goals instead: transcript materialization is still not separately attributable from `config_setup`, which needs instrumentation inside the core session loader that P1/P2 restructures anyway, and sibling event-loop latency during a large restore remains unmeasured. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): bound the condemned-channel close and complete the fence contract Second automated review round, on the code the first round produced. One Critical and twelve suggestions; all verified by mutation before and after. **The Critical is a regression I introduced.** Letting a condemned channel skip the bounded hold probe routed it into `closeSessionImpl`, whose agent close is unbounded when it throws on failure — so the fix traded a bounded wait on a wedged child for an unbounded one. A settlement-overdue channel with an unresponsive child would hang `detachClient` forever, strand the session in `closing`, never drain, never reap, and 503 every new session until restart: strictly worse than before. `CloseSessionOpts` now carries an `agentCloseTimeoutMs` that the condemned path sets, so a hang lands in the existing unknown-outcome recovery, which kills the channel — the teardown the drain was waiting for. The earlier test missed this because its fake child still answered the plain close; it now answers nothing at all, and asserts the detach itself returns. **The fence was invisible on the transports clients actually use.** `toRpcError` had no `RestoreInProgressError` case, so over acp-http and acp-ws — which SDK negotiation prefers over REST — the fence degraded to an opaque internal 500 with no code, reason, or hint, and the backoff contract this work documents was impossible to honor. **Two retry hints still advertised five seconds for states that outlive a budget.** The restore 504 creates the fence, and quarantine lasts until the channel drains; a fresh-id caller never reaches the 409 that carries the real hint, so its header was the only signal it got. Both now derive from the budget through one shared clamp helper, which also replaces the formula that was inlined in the bridge and gives the documented 5-120s bounds a test. **A spawn collision reported an operation the caller never issued**, naming the restore owner's action as both the active and the requested one and telling the caller to retry an endpoint it never called. The rest: five places still described the initialize-timeout fallback as a plain chain rather than raise-only, contradicting sibling docs shipped in this same PR; the design doc omitted the retry-hint clamp; the protocol reference omitted the new spawn emission site; the error taxonomy omitted `restore_settlement_overdue`, which matters because its audience is monitoring. Test-only gaps: the dynamic 409 had no HTTP-layer coverage, the 120-second cap was unpinned, and the SDK's precedence of an explicit global timeout over the advertised budget was pinned only branch-by-branch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(serve): preserve restore session ownership handoff Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
3c3084e78a
|
fix(ci): route workflow label mutations through REST (#8761)
* fix(ci): route workflow label mutations through REST `gh pr edit` cannot mutate anything on this repository: its GraphQL lookup requests repository.pullRequest.projectCards, and with Projects (classic) attached GitHub returns the deprecation as an error, so the command exits 1 before applying the change. Reproduced from a live clone against PR #8755 — the error names the field outright. Three workflows carried label mutations through it: - pr-self-report-label.yml: every add/remove arm failed — 43 straight run failures from 2026-08-04 on; the green runs were all the nothing-to-do arm. Self-reported PRs (like #8755, whose author also opened #8750) never got the label. - qwen-autofix.yml: the `@qwen-code /takeover` and `/takeover stop` COMMAND paths never toggled the label — only the UI label events worked, so the command was dead weight wearing an ack. - repo-hygiene.yml: the add was `|| echo`-guarded, so it never failed the run — it just never labeled anything, while the fallback message blamed a label that exists. All five sites now use the REST issues/labels endpoints, which never touch that query. Two traps handled on the way: - Every label involved contains a slash, and in the DELETE the label is a PATH SEGMENT — unencoded it 404s. Encoded via jq @uri, and the tests assert the literal %2F because a real jq runs in the replay. - The REST add auto-creates a missing label, which repo-hygiene explicitly promises never to do — that site gets an existence probe first, and its misdiagnosing fallback message is corrected. Verified live on #8755 before editing anything: the exact gh pr edit call fails with the projectCards error; REST POST applies the label (backfilling the one it was owed), DELETE with %2F removes it. Tests: the stub-driven replays for both the self-report step and the takeover toggle now pin the full REST method + path (encoding included), and a repo-wide guard bans `gh pr edit --add-label/ --remove-label` in every workflow so the class cannot return. Mutation-tested, 6 of 6 caught: each of the five sites reverted to gh pr edit, and the DELETE stripped of its encoding. * fix(ci): harden REST label mutation steps per review (#8761) * fix(ci): pin REST label failure policies per review (#8761) Review round for the REST migration: - The DELETE arms tolerated EVERY failure (`|| true`), masking 403/5xx/network errors behind a green run and a false "removed" log. They now tolerate only the documented 404 race — any other failure emits a :⚠️: while keeping the step green (pr-self-report-label) and the release ack alive (qwen-autofix). - Neither replay harness could make a `gh api` call fail, so both failure policies were unpinned. They gain failure knobs (knob value on stderr like a real gh HTTP error) and now pin: 404 race silent, other DELETE failures warned, POST loud. The toggle replay also moves to -eo pipefail like the runner's bash default, reproducing the step's real failure semantics. - The jq stub enforced only the --arg shape; it now also enforces the `$l|@uri` program, so a filter mutation fails the suite instead of riding the stub's unconditional percent-encoding. - The gh-pr-edit guard misfired on comments and miscounted lines after joining continuations: comments are stripped before matching, and offenders are reported at the physical line where the (possibly wrapped) command starts. Mutation-tested with 8 probes, all caught: blanket || true on either DELETE, || true on either POST, dropped |@uri, a comment quoting the ban (stays green), an executable and a wrapped violation (both red, correct line). * Address review round 3: close the guard evasions, convert the release path Four round-3 findings, each reproduced before fixing, plus the release path the round-1 scope note deferred. - The ban guard now scans what bash executes, not the YAML surface: the decoded run: values of every parsed workflow, whole-line comments stripped, continuations joined the way bash joins them (backslash- newline removed, nothing inserted), matched whitespace-tolerantly. All three reproduced evasions — a # inside a quoted string eating the trailing backslash, wraps inside the command prefix or a flag token, and folded scalars — are fixture-pinned. Offenders report as file » job » step; line numbers stopped meaning anything after joins. - classify-release-notes.mjs mutates labels through REST now, and the guard grew an argv-form scan over .github/scripts/*.mjs that flags the old file (negative-controlled) — the release path was the last gh pr edit label site, failing silently behind continue-on-error. - JQ_STUB enforces the full invocation: -rn (with -r alone real jq evaluates zero inputs and prints nothing), the binding name l (real jq exits 3 on $l undefined), and the program. Either reproduced mutation previously expanded the substitution empty, sent the DELETE to …/labels/ with no name segment, and the 404 tolerance swallowed it. - The takeover engage POST gets the idempotent create its siblings carry, pinned to the label's real color (1D76DB): the REST add would re-create a deleted label silently with a random color. - runToggle captures writes on throw, and the engage-failure assertion now pins the ORDER its comment claims: a failing apply must leave no "takeover-ack engaged" in the captured writes — the bare toThrow passed even with the ack moved above the POST (reproduced). - The two REMOVE_ERR DELETE idioms are drift-pinned byte-identical modulo the label variable, the honest substitute for sharing shell across workflow files. Mutation-tested, 6 of 6 caught: the evadable regex restored, -rn and the binding name mutated in the workflow, the create dropped, the ack posted before the POST, and the old .mjs flagged by the new scan. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
b7eb4cf29e
|
fix(desktop): compact Local Control dialog (#8800) | ||
|
|
e39781d858
|
feat(web-shell): add fullscreen view for the right artifact panel (#8614)
* feat(web-shell): add fullscreen view for the right artifact panel
The right panel (artifacts / subagents / review) is narrow, so long
previews and transcripts are hard to read. Add a fullscreen toggle to
the panel header that expands the panel to cover the viewport; pressing
Escape or the toggle again shrinks it back to its dock or drawer.
* test(web-shell): capture fullscreen artifact panel in visual previews
The fullscreen toggle and surface are only reachable with the artifact
panel open, so no visual scenario rendered them and the before/after
preview could not see this feature. Extend the code review artifact
scenario to expand the panel fullscreen, capture it, and assert the
Escape restore path.
* fix(web-shell): keep artifact panel mounted across fullscreen toggle (#8614)
* fix(web-shell): harden artifact panel fullscreen per review feedback (#8614)
* fix(web-shell): guard artifact drawer Escape for IME composition (#8614)
* fix(web-shell): scope toast z-index, reset dock animation flag, guard IME keyCode (#8614)
Round-4 review fixes for the artifact panel fullscreen:
- Elevate ToastHost above dialog-backdrop-tier surfaces only while the
fullscreen surface is up (new `elevated` prop); otherwise it stays at its
original z-index 30 so DialogShell modals keep painting above toasts. The
old comment rested on DialogShell.module.css's dead `.backdrop` class —
the live modal path resolves the same host variable with fallback 50.
- Reset suppressArtifactDockOpenAnimation when the dock unmounts (panel
close or floating drawer taking over) and only set it while docked, so a
floating<->docked flip after a fullscreen round-trip no longer mounts the
dock without its slide-in animation.
- Extend the window Escape handler's IME guard to keyCode 229, matching the
paired guard used everywhere else in the package: a WebKit-shaped IME
Escape (isComposing false, keyCode 229) previously shrank a fullscreen
panel and swallowed the native IME cancel.
Adds the suggested coverage: keyCode-229 drawer variant, docked-fullscreen
IME Escape, ask-user shrink keyboardActive history, streaming+fullscreen
Escape ordering, and the floating-interlude dock animation regression.
* fix(web-shell): portal docked fullscreen surface, gate chat shortcuts, clamp z-index (#8614)
Round-5 review fixes for the artifact panel fullscreen:
- Clamp the fullscreen surface z-index floor to max(1, backdrop - 10): a
host setting --web-shell-dialog-backdrop-z-index below 10 gave the
surface a negative z-index, painting the opaque panel behind the app
background and blanking the shell.
- Add artifactPanelFullscreen to interactionBlocked: chat-only global
shortcuts (Ctrl+L/O/Y, Shift+Tab, the btw hotkey) kept mutating the
hidden chat behind the surface, and the btw capture-phase Escape handler
dismissed hidden content and swallowed the Escape that shrinks the panel.
- Pad the floating drawer fullscreen content with env(safe-area-inset-*):
the portaled drawer sits outside the padded app root, so on
viewport-fit=cover devices the toolbar and Exit control could sit under
the notch/status bar and bottom content behind the home indicator.
- Move the docked fullscreen surface into the top-level portal root and
give it document-level modal semantics: a transformed, paint-contained,
or lower-stacking host ancestor could bound the fixed panel or paint
over it, and Tab could escape into covered host controls. The panel
wrapper portals into a display:contents slot that the fullscreen effect
parks in the portal root, so the SAME node survives the move (panel
state preserved, React event delegation intact); FocusScope provides the
Tab containment and the effect hides every outside tree from AT and
captures stray focus — matching what the floating variant gets from
vaul's Radix dialog. Declares @radix-ui/react-focus-scope (already in
the tree via radix-ui) as a direct dependency.
- Portal the elevated ToastHost into the portal root: in shadow-DOM portal
mode the fullscreen drawer surface is sealed inside the portal host's
stacking context, so an in-tree toast painted beneath it for its whole
auto-dismiss lifetime.
- Keep the dock animation suppression flag across dock<->floating
hand-overs performed mid-fullscreen in both directions, so shrinking
back to the dock never replays the slide-in on the already-open panel.
Tests: pin both hand-over suppression directions (mutation-verified), the
portal placement, the interaction gate, and toast elevation in the main
fullscreen test; add a colocated ToastHost.test.tsx for the elevated
class; fix the dead 0-measurement step in the docked-width round-trip
test (the 0 is now seeded before entering fullscreen, where the clamp
effect can actually observe it). Adds a drawer-fullscreen visual scenario
at a narrow viewport in both themes, with Escape restoring the drawer.
* fix(web-shell): repair docked fullscreen panel modal semantics (#8614)
* fix(web-shell): reset fullscreen state in the panel close commit (#8614)
Closing the artifact panel while fullscreen reset the fullscreen flag
only in the passive management effect, so one render committed with the
panel unmounted while the covered shells stayed display:none — one
painted frame of an empty shell before the chat reappeared. Batch the
fullscreen and dock-animation-flag resets into closeArtifactPanel and
the last-tab close so the recovery happens in the same commit.
Adds a regression test asserting the shells are revealed in the close's
committed frame (fails without the batched reset), and pairing coverage
for the drawer Escape pass-through: a plain Escape still closes the
floating drawer when the panel was never fullscreen.
* fix(web-shell): pull escaped focus back into the docked fullscreen surface (#8614)
* build(external-context): restore the node-only types override from main
Cherry-picks the tsconfig guard from
|
||
|
|
f3ba99f545
|
fix(sdk): hide ACP usage updates from transcripts (#8790)
* test(sdk): reproduce visible usage updates * fix(sdk): hide ACP usage updates from transcripts |
||
|
|
3e731cda8b
|
fix(test): deflake three CI-load-sensitive tests (#8797)
* fix(core): deflake auto-memory extract tests under CI load waitForMockCall polled ten zero-delay event-loop turns and gave up. The mock it waits for fires after real async work (index rebuilds, cursor I/O), so on a loaded CI runner the poll spun through its ten turns without waiting any wall-clock time and the two rebuild-isolation tests failed with 'Expected mock to be called' (seen on PR #8773's Test (ubuntu-latest) job). Wait against a 2s deadline instead; the fast path still returns on the first check. * fix(cli): give the manifest-context fixture teardown a real timeout The afterAll deletes several 16k-entry fixture trees — tens of thousands of unlinks — and blew past vitest's default 10s hook timeout on a loaded CI runner (PR #8773's second Test (ubuntu-latest) run), failing a suite whose 59 tests had all passed. Same CI-load flake class as the extract test fix in this branch. * fix(core): let the shell-registry retention tests pay for their sidecar I/O Each register/complete in the retention-cap loop tests also writes the status sidecar via atomicWriteFileSync, and loaded CI runners have been measured at ~700ms per sidecar write — the ~70 writes of the longest loop take ~50s, past vitest's 15s default, failing four tests whose assertions are pure eviction semantics (seen twice on PR #8773, on two different runners). Give them an explicit 120s timeout; no assertion changes. |
||
|
|
bf84caf173
|
feat: add Local Control pairing to CLI and Desktop (#8727)
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 / 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
E2E Tests / channel-plugin E2E (nightly) (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
npm cache producer / Save npm cache (push) Has been cancelled
* feat(cli): add Local Control pairing * fix(cli): address Local Control review feedback * fix(cli): allow Local Control loopback origin * feat(desktop): add Local Control pairing * fix(local-control): bound unauthenticated connections * test(desktop): allow Windows proxy cleanup * test(desktop): avoid socket cleanup timing * fix(desktop): surface Local Control status * fix(desktop): simplify Local Control window * fix(desktop): harden Local Control pairing * fix(desktop): bind Mac wake lock to app |
||
|
|
d8c15b3456
|
fix(tests): await rig setup in Qoder plugin install integration test (#8793) | ||
|
|
ab61d81097
|
fix(desktop): enable microphone access on macOS (#8715)
* fix(desktop): enable microphone access on macOS * fix(desktop): narrow helper entitlements * test(desktop): pin macOS permission packaging * test(desktop): strengthen entitlement release guards |
||
|
|
10621b3a93
|
fix(external-context): read the response body with a reader, not for-await (#8764)
* fix(external-context): read the response body with a reader, not for-await Async-iterating a ReadableStream needs [Symbol.asyncIterator] on the TYPE, and whether it is there depends on which lib set the program resolves — @types/node's stream has it, the DOM lib's needs lib.dom.asynciterable. That resolution flipped underneath this file on 2026-08-08: #8693 installed @types/jsdom at the root, vitest's types pull the jsdom types in wherever they exist, and jsdom's carry /// <reference lib="dom" />. #8693 shipped the tsconfig `types` guard in the same commit, so main stayed green — but the guard travels with the BRANCH while node_modules travel with the TRUSTED BASE in the autofix verification build, so every managed branch behind #8693 failed that build with TS2504 on this line. Two legs measured on run 31276008548: 63 minutes of accepted agent work discarded per round, 18 more minutes burned by a repair step that cannot fix a failure outside the PR's diff (#8614 reached attempt 13 that way; #8616 died identically). Reproduced locally in both directions before changing anything: @types/jsdom installed + guard removed = the gate's exact error, character for character; with the reader loop the same poisoned setup builds clean. The guard stays — belt and suspenders — but the build no longer depends on it, or on which lib set any future environment resolves. Behavior is unchanged and now pinned by tests the file never had: multi-chunk assembly, the exact MAX_RESPONSE_BYTES boundary (bound is strictly-greater), invalid-UTF-8 rejection, and the easy one to drop in this rewrite — cancelling the stream on early exit, which `for await` did implicitly via iterator return(). Mutation-tested: removing the cancel fails exactly that test against an endless producer. The package's other for-awaits iterate process.stdin (a Node stream, async-iterable in every lib set) and are untouched. * fix(external-context): await stream cancellation before rejecting the request On early exit from the reader loop (the oversize throw) cancellation was started fire-and-forget, so postJson() rejected while the stream's teardown was still settling — `for await` had awaited its implicit iterator return() before propagating. An immediate retry could overlap the previous response transport's unfinished cancellation. Await reader.cancel() before releaseLock(), and pin the sequencing with a deferred-cancel regression test that fails against the fire-and-forget form. Also cover read() rejecting after a partial chunk was received: the error maps to the request-did-not-complete transport error rather than EOF-then-parse of the partial JSON, and the reader lock is still released. * fix(external-context): drop the types guard the reader rewrite made obsolete The `"types": ["node"]` override existed solely to keep @types/jsdom's lib.dom out of this program while http-client.ts read the response body with `for await` — the DOM lib's ReadableStream is not async-iterable, and the flip broke the build with TS2504 (#8693). The reader loop that replaced the `for await` types identically in every lib set, so the guard is no longer load-bearing: with it removed, lib.dom re-enters the program and the package still builds cleanly. Drop it with its stale comment instead of leaving maintainers two contradicting stories about whether it is needed. Also export MAX_RESPONSE_BYTES and import it in the boundary tests instead of re-declaring it locally, so the tests pin the real constant rather than a copy that can silently drift. * test(external-context): make the invalid-UTF-8 test pin fatal decoding --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
5e83efb555
|
fix(serve): stop usage_update frames from flooding the demo event log (#8762)
* fix(serve): stop usage_update frames from flooding the demo event log The demo page's SSE fallthrough logs every unrecognized session update as raw JSON. usage_update is emitted once per main-session model round, so a long agentic turn (/review runs hundreds of rounds) turned the Events tab into a scroll of identical usage_update lines. Render the frame as what it is instead: an in-place context meter in the session panel (used / size, percentage), reset on session create or attach, with an Events entry only when the integer percentage moves — the transitions stay on record without the flood. * test(cli): cover demo usage_update dedup logging (#8762) * test(cli): pin usage_update size type guard and meter reset (#8762) * fix(cli): align demo context meter with CTX log format (#8762) The meter label used toLocaleString() while the CTX log entry used plain concatenation, so the same usage_update frame rendered differently once token counts passed 1000. Build one label and reuse it in both places, and pin the rounding, downward-transition, >1000 formatting, and the script-scoped dedup declaration in the jsdom tests. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
60458f5e37
|
fix(serve): Coordinate caller-supplied session IDs (#8415)
* fix(serve): coordinate caller-supplied session IDs Complete daemon-wide admission across REST, ACP, workspace generations, SDKs, and MCP. Closes #8411 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): wire session bridges in hot-reload harness Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): address review round for caller-supplied session IDs (#8415) Restore the observability and fail-loud guarantees flagged in review: log every session-id admission routing failure, name the live foreign owner workspace in restore conflicts, make the ACP dispatcher's admission dependency required so load/resume cannot run on a mount without one, and require mountAcpHttp hosts to inject the daemon-wide admission instead of silently building a weak fallback. Harden the SDK WS transport against environments without global fetch and against non-capabilities 200 envelopes, and align the design doc with the implemented restore-sharing and persistence-failure semantics. * fix(sdk): harden session ID capability fallback Preserve REST capability errors, fail closed on malformed envelopes, retain restore routing diagnostics, and align retry documentation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): normalize restored session IDs Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(session): preserve mixed-case legacy session access Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
afcc937ec5
|
fix(ci): render the queued-acknowledgement comment (#8726)
* fix(ci): render the queued-acknowledgement comment The ack comment posted on every PR that requests a review was built as <!-- qwen-review-ack -->_Qwen Code review request accepted. …[workflow run](URL)._ with the prose glued straight onto the marker. A line opening with `<!--` starts an HTML block, and that block runs to the line containing the closing delimiter INCLUSIVE — the rest of that line is still inside it and never parsed as Markdown. So the comment shipped as raw source: no emphasis, and the link to the workflow run was dead text. That link is the only pointer a PR author gets to their review run — `issue_comment` runs are not attached to a commit, so they never appear in the PR's checks list. Losing it leaves no way to reach the run from the PR. Measured through GitHub's own renderer (POST /markdown, mode=gfm) on the exact bodies: marker + text -> 0 <a>, 0 <em> marker + \n + text -> 1 <a>, 1 <em> marker + \n\n + text -> 1 <a>, 1 <em> Use the blank-line form, matching how autofix-status already builds its body. The marker text is unchanged, so the `contains(...)` upsert lookup still finds prior acks and updates them in place. Pinned by a test that scans every marker in the workflow and rejects one with prose glued to it, skipping comment lines. It fails against main, naming the offending line. * fix(ci): harden the marker guard per review round 2 Pin the workflow-run URL weaving into the ack printf, bound the marker scan to the marker's physical line, anchor the newline exemption to the literal the marker opens, widen it to double-quoted printf formats, and flag unquoted command-substitution concatenation. Declare the remaining coverage gaps in the test instead of papering over them. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): widen marker-guard regex and declare known gaps per review * fix(ci): pin ack link shape and dedupe workflow scan per review --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |