Commit graph

311 commits

Author SHA1 Message Date
Bob.qwencode
c10143a9c1
chore(release): v0.22.0 (#9736)
* chore(release): v0.22.0

* docs(changelog): sync for v0.22.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-22 15:23:02 +00:00
易良
e40263ee55
chore(deps): Clear high-severity CVE baseline and harden the security gate (#9584)
* chore(deps): Clear high-severity CVE baseline and harden the security gate

- Bump OpenTelemetry stack to 0.221.x (fixes @opentelemetry/core advisories)
- Bump @larksuiteoapi/node-sdk to ^1.73.0 and override axios to ^1.19.0
- Bump mobilewright to ^0.0.53 (drops vulnerable sharp 0.34.x)
- Bump markdown-it to ^15.0.0 (drops vulnerable linkify-it 5.x)
- Update undici/fast-uri/brace-expansion/ip-address within range
- Adapt telemetry code to OTel API changes (forceFlush, processor options)
- Make security-checks a hard gate now that the high baseline is clean

* chore(deps): Refresh mobile-mcp vendored lockfile to drop vulnerable sharp

* fix(telemetry): stub sdk-node 0.221 env auto-config helper packages

sdk-node 0.221 extracted its env-based auto-configuration into
@opentelemetry/configuration, otlp-exporter-base, and
otlp-grpc-exporter-base, which it now requires eagerly. The existing
esbuild stub only covered the exporter-* packages, so the OTLP protocol
chain (grpc-js, protobufjs, otlp-transformer) re-entered the sdk-impl
static closure and tripped the serve fast-path bundle guard.

Stub the three helper packages when imported by sdk-node only; our own
protocol modules keep resolving the real packages. qwen-code never
reaches these helpers at runtime (explicit exporters + env scrub).

* fix(telemetry): disable metrics fallback without reader

* fix(vscode): restore nested dependency notices

* fix(deps): declare bundled punycode so its notice survives regeneration

The CLI esbuild config aliases punycode to the userland package
(esbuild.config.js), so the shipped CLI bundle contains MIT-licensed
punycode@2.3.1. Its NOTICES.txt section was lost because the only
lockfile paths reaching punycode were dev-only; the notice walker
(rooted at vscode-ide-companion) never sees a production declaration.

Declare punycode as a direct production dependency of the CLI (the
bundle input) and of vscode-ide-companion (which packages the bundled
CLI into the VSIX and owns NOTICES.txt), then regenerate the lockfile
and notices so the MIT notice is restored.
2026-08-21 07:43:32 +00:00
Bob.qwencode
808c9c9f3d
chore(release): v0.21.14 (#9594)
* chore(release): v0.21.14

* docs(changelog): sync for v0.21.14

* docs(changelog): sync for v0.21.14

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: 易良 <1204183885@qq.com>
2026-08-20 15:21:01 +00:00
jinye
3378212b5f
feat(cli): correlate daemon logs with OpenTelemetry spans (#9084)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-13 16:33:12 +00:00
qwen-code-ci-bot
5af31316aa
chore(release): v0.21.11 (#9054)
* chore(release): v0.21.11

* docs(changelog): sync for v0.21.11

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-13 05:41:43 +00:00
Shaojin Wen
187637449b
feat(review): cover modeled-system defect layers in the reverse audit (#8956)
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 / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(review): cover modeled-system defect layers in the reverse audit

A diff that models how an external system executes — a shell/git guard, a
sandbox, a permission interpreter — has a defect class the fixed dimensions do
not name: divergence between the model and the real system's STATE semantics
(what survives a function/eval/subshell/substitution boundary), not just its
syntax. That class is non-local and needs a differential oracle, so a static
single-model pass under-covers it, and the reverse audit's "two dry rounds"
stop rule is silent about any layer nobody walked.

Add a defect-layer lens across three sides, each independently revertible:

- Finder: the security pass gains a model-of-execution divergence hunt (run the
  real system as an oracle to discover it), and the invariant checklist gains a
  recursive-evaluator state-return contract for the cross-chunk half.
- Coverage: the reverse-audit brief asks each defect layer be walked and
  receipted on its own line; a taxonomy parses those receipts into per-layer
  coverage, and a standalone command reports it for A/B measurement.
- Cap: a deterministic gate emits one unreviewed-dimension entry per unwalked
  layer, capping a would-be Approve. It is opt-in via a repository-context
  domain, model out of the loop, and one-directional — it never ends the loop,
  blocks a Request changes, or changes convergence.

Extending the convergence rule itself so an unwalked layer keeps the loop
running is deferred behind an A/B on real modeled-system PRs.

* fix(review): fail-open the layer gate on a missing transcript dir

Address the review on this PR.

- Blocker: the gate's transcript read wrapped only the plan stat, not
  readTranscripts, which throws when the transcript directory is absent — so a
  manifest-marked diff in a transcript-less environment (a sandbox, a read-only
  HOME, a re-compose on a clean machine) crashed compose and posted nothing,
  contradicting the gate's own fail-open header. Wrap the whole read; add a test
  that exercises the real reader against a missing dir, which the injected-reader
  tests could not reach.

- Scope the automated cap to the shell/git model honestly: the brief and header
  promised a manifest-declared layer taxonomy for non-shell modeled systems that
  no channel supplies, so arming the sentinel on such a diff would owe the shell
  layers forever. Narrow the prose to what ships and name the manifest-taxonomy
  wiring as the follow-up that lifts the limit.

- Prefix the owed entry `reverse-audit layer coverage — ` so compose-review's
  caller-echo dedup cannot shadow the per-layer disclosures behind a `reverse
  audit` coverage subject; the verdict cap was already unaffected.

- Use the depended `glob` package in the measurement script instead of the
  experimental node:fs/promises glob.

* fix(review): corroborate and identity-anchor the layer-coverage reader

Address the second review round. The gate measured coverage from auditor prose
too readily, so a modeled-system diff with unwalked layers could release Approve
— the exact failure this feature prevents. Three probe-verified holes, all
closed, plus an end-to-end test that pins the cap through the real reader.

- Corroborate before a receipt counts: a transcript's receipts are read only
  when the harness's tool-call record shows it actually read the diff
  (diffToolCalls > 0, retirement's bar). A brief-only parrot holds every layer
  id from its own brief and can emit all six receipts without walking a layer;
  it has diffToolCalls === 0 and is dropped. successfulToolCalls > 0 would not
  drop it — the brief read is a successful call.

- Anchor the auditor selector on the launch IDENTITY line rather than a bare
  `reverse-audit` substring, which counted any transcript merely mentioning the
  role — a verifier inlining reverse-audit findings and quoting their receipt
  lines, a nested subagent — and pulled its finalText into the pool.

- Harden the receipt parser: a marker inside an inline code span or an indented
  code block is quoted, not used, and no longer parses as a live receipt. Allow
  a digit in a layer id so a custom taxonomy is not silently truncated.

- Refresh the now-stale module header (the cap ships, it is no longer "the next
  increment"), scope the arming docs to the shell/git layer set, and qualify the
  3B coverage claim (invariant-c runs only on heavy files; the cross-chunk
  contract backstops on the reverse-audit receipts).

The new compose-review cases exercise the real reader end to end: a partial-walk
auditor caps Approve to Comment, a full walk stays Approve, and neither a
diff-blind parrot nor a mis-identified verifier is counted.

* fix(review): track fences the CommonMark way in the receipt parser

Address the third review round.

- Critical: the receipt parser's symmetric fence toggle diverged from CommonMark
  three probe-verified ways, each releasing a QUOTED `Layer walked:` marker as a
  live receipt — a mismatched fence line (`~~~` inside a ``` block, or a shorter
  run) closed early, a list-item fence never opened, and a fence line with
  trailing content closed a block GitHub keeps open. Replace it with fence
  tracking that records the opening character and length, opens generously (0-3
  spaces, optional list prefix) and closes strictly (same char, >= length,
  whitespace only), biased toward skipping. The shared `usedLines` walk now backs
  both the parser and the `--infer` estimate, so neither credits a layer from
  quoted text.

- Correct the docs that overclaimed 3B coverage: Agent 2 does not run on a
  territory fan-out and the chunk agents do not inherit its brief, so the
  execution-model lens is 3A-only; on a huge diff the reverse-audit receipts and
  the cap carry the class, with invariant-c a heavy-file backstop.

- Fix the module header (coverage is the receipt, not a bare finding) and the
  test name that echoed it.

- Pin the gate's identity anchor against the real launch-prompt builder, so
  rewording the header cannot silently stop the gate selecting an auditor. Allow
  a digit in a layer id.

Deferred (non-blocking test gaps, noted in the thread): a test for the run-epoch
mtime fence, and extracting the measurement script's round-sort for a unit test.

* feat(review): carry the execution-model lens into 3B chunk agents

Two gaps a modeled-system review left open, both surfaced by the ongoing review
of the cross-worktree guard.

- The finder-side execution-model lens ran only on a 3A dimension fan-out; on a
  3B territory fan-out Agent 2 does not run and the chunk agents did not inherit
  its brief, so a huge guard/interpreter diff — the band this class lives in —
  got no finder coverage. Extract the lens into one exported constant and attach
  it to each chunk agent when the manifest declares the diff a modeled executable
  system, scoped to the chunk. Agent 2 still carries the same constant on 3A, so
  there is one source for both topologies. The cross-chunk contract still falls
  to the reverse-audit receipts and invariant-c.

- The state layers named only the ESTABLISH side of shell state. A model that
  grows an add-only map of function/alias definitions, export attributes, or
  options and never removes an entry diverges the moment the real shell removes
  one (`unset -f`, `unalias`, `export -n -f`, `set +a`). Name the removal side in
  the resolution-order and inheritance layer hints, in the lens's second bug
  shape, in the reverse-audit walk, and in invariant-a's collection check, so an
  auditor is led to check the removal path for every add path — the exact class a
  reviewer found when the guard's `definedBodies` map gained entries but modeled
  no removal.

* fix(review): close the layer gate's corroboration and fence leaks

Address the fourth review round — three release-direction gaps and cleanups.

- Critical: the receipt parser's fence tracker closed a list-item fence at any
  0-3 space indent, so a shallower closer released the quoted markers after it.
  Record the opener's indent (its content column) and close only at that column
  or up to three past it; a shallower or unrecognised closer keeps the fence
  open, biasing every remaining indent corner toward skipping.

- The corroboration bar was range-blind: `diffToolCalls > 0` passed an auditor
  that read a far chunk and then parroted its receipts. Add retirement's other
  half — the diff read must overlap the territory the launch prompt baked
  (`openedTheTerritory` + `bakedRanges`, now exported); a whole-diff auditor
  bakes none and still passes on the read floor.

- The empty-pool branch deferred to the reverse-audit-ran floor, but that floor
  has no diff-read requirement — so a run whose auditors all ran yet none read
  the diff went uncapped. Distinguish "could not measure" (fail-open) from
  "measured: auditors ran, none corroborated" (owe every layer).

- Cleanups: rehome the parseLayerReceipts JSDoc that had stranded above the
  fence helpers, drop the unused exported `coveredBy`, and pin the invariant-a
  removal clause with a test.

* fix(review): locate quoted regions with a real CommonMark parser

The receipt parser's hand-rolled fence/quote scanner diverged from CommonMark
round after round — each review pass probed another corner (mismatched fences,
list-item containers, trailing content, tab stops, HTML blocks, nested
blockquotes), and each gap released a quoted `Layer walked:` marker as a live
receipt. A second parser is a divergence hunt, and this skill's own rule is that
the oracle must come from the authority the code models, not a self-consistent
re-implementation.

Replace the scanner with `markdown-it` (already a workspace dependency, the
parser GitHub's own family uses): tokenize the return and treat every line inside
a fenced/indented code block, an HTML block, or a blockquote as quoted, reading
the block tokens' own line ranges. The receipt regex still guards inline
code spans (no leading backtick). This ends the divergence class outright —
HTML blocks, tab-indented code and nested blockquotes now quote their markers
with no new code, and the obsolete hand-rolled-closer test (which pinned a
divergent expectation) is replaced by cases verified against the parser.

Also (R4-5): stop attaching the modeled-system lens to an unreachable chunk,
whose one instruction is to return `Uncoverable:` and stop.
2026-08-12 18:15:11 +00:00
qwen-code-ci-bot
cb528e52c9
chore(release): v0.21.10 (#8942)
* chore(release): v0.21.10

* docs(changelog): sync for v0.21.10

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-11 14:30:20 +00:00
qwen-code-ci-bot
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>
2026-08-10 15:27:45 +00:00
易良
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 / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
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
2026-08-09 08:31:54 +00:00
qwen-code-ci-bot
4d6246bd88
chore(release): v0.21.8 (#8757)
* chore(release): v0.21.8

* docs(changelog): sync for v0.21.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-08 19:25:22 +00:00
qwen-code-ci-bot
fca8f3c1f1
chore(release): v0.21.7 (#8655)
* chore(release): v0.21.7

* docs(changelog): sync for v0.21.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-07 00:19:20 +00:00
qwen-code-ci-bot
5173052e37
chore(release): v0.21.6 (#8598)
* chore(release): v0.21.6

* docs(changelog): sync for v0.21.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-05 16:11:17 +00:00
qwen-code-ci-bot
e946fdbd3b
chore(release): v0.21.5 (#8505)
* chore(release): v0.21.5

* docs(changelog): sync for v0.21.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-04 02:30:44 +00:00
qwen-code-ci-bot
0cbb6a1f1c
chore(release): v0.21.4 (#8424)
* chore(release): v0.21.4

* docs(changelog): sync for v0.21.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-03 04:52:58 +00:00
qwen-code-ci-bot
893a97057b
chore(release): v0.21.3 (#8338)
* chore(release): v0.21.3

* docs(changelog): sync for v0.21.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-01 18:14:12 +00:00
Shaojin Wen
1be199ae82
feat(review): render adjudication, workflow step extraction, three verification lenses (#8225)
* feat(review): borrowed-verification trio — test-plan check, base-tree A/B, per-hunk probes

- qwen review test-plan: rule on the PR Test Plan's checkable claims (paths,
  npm scripts, test counts) against the reviewed tree; contradictions and
  differing counts are disclosed via compose-review, never capping.
- qwen review base-tree: build the merge base in a sibling worktree so the
  verifier can A/B a comparative claim instead of reading it; swept by cleanup.
- test-efficacy: third probe kind — reverse-apply one hunk at a time and
  re-run the affected tests, attributing a still-green suite to the specific
  change nothing gates; shares the mutants' budget window, runs last.

* fix(review): survive real runner output — ANSI-laced and trimmed-away summaries

Both measured on a live /review of QwenLM/qwen-code#8176 with the built CLI:

- test-plan's observedTestCounts strips SGR sequences before matching; a
  color-enabled pipe interleaves them BETWEEN tokens, and the count claim
  fell to 'unchecked' with the summary right there in the report.
- build-test's trimOutput rescues runner summary lines from the omitted
  middle (like module-resolution errors): a failing suite's tail is all
  failure details and npm epilogue, which pushed the one-line summary out
  of the kept text entirely.

* fix(review): address the eight findings from live review of this branch

All measured in the review (QwenLM/qwen-code#8215 review comment):

- test-plan: linear-time bold-heading scan (the old pattern backtracked
  catastrophically on an unclosed ** line an untrusted PR body controls);
  a flag preceding the npm script yields no claim instead of a false
  'no package defines this script'.
- test-efficacy: a hunk probe's restore recreates the parent directory a
  reverse-applied 'new file' hunk removed (the ENOENT from finally lost
  the verdict and marked every remaining hunk inconclusive); hunks get
  their own skippedForBaseline instead of mislabeling a red baseline as
  a budget skip; splitDiffIntoHunks re-captures the file header at every
  diff --git boundary; a hunk-survived finding notes when it restates an
  inert file-level revert at hunk granularity.
- base-tree: idempotent fast path keyed on a build marker + HEAD check —
  concurrent verifier shards reuse one built tree instead of sweeping it
  out from under each other mid-A/B (a fabricated base-side difference
  with a deterministic source tag was the worst case); cost wording is
  now 'an install and a build' everywhere it was 'one extra build'.

* fix(cli): never score a hunk survived when its own test left the baseline (#8215)

A per-hunk probe reported `survived` whenever the green baseline probes still
passed with the hunk reverted. When the hunk's own collocated test dropped out
of the baseline (a probe-tree import error collects nothing), the remaining
green probes prove only that THEY do not cover the hunk, so the verdict is now
`inconclusive` — the same dropped-test asymmetry the mutants already hold.

Also scope the hunk-survived cross-reference note to the hunk's own collocated
test, and let test-plan match a workspace-scoped run of the plan's bare command
instead of falling through to the manifest on an exact-string miss.

* fix(review): silence-bias hardening from four live review rounds of this branch

The two blocking findings, reproduced on this PR's own Test Plan:

- test-plan files no false contradicted notes: npm rulings move from a
  four-verb denylist to an allowlist (the run form + npm's script aliases —
  the ~fifty other builtins each used to become 'no package defines this
  script'); a slash token is claimed as a repo path only with evidence (an
  extension or ./ prefix), never when it is a flag's value (--repo
  owner/repo) or under the review's own temp root; HEADING_LINE_RE drops
  the same quadratic shape its bold sibling was rewritten to remove.
- base-tree gets a real mutual-exclusion lock around sweep+add+build
  (mkdirSync test-and-set; the loser returns busy instead of deleting the
  tree the winner is mid-install in), and a failed build writes a settled
  marker so later shards stop re-paying the install to relearn
  'unavailable'.

Also: Agent 7's brief now names hunk-survived and the hunks.* counters
(it is the report's only consumer, and the finding class was invisible);
hunk findings anchor at the first ADDED line instead of up to three
context lines above the change.

* feat(review): four round-2 borrowings — measured failure attribution, round ledger, richer mutants, doc parity

Shaped by a live round-2 maintainer re-verification (QwenLM/qwen-code#7818):

- qwen review test-delta: rerun the PR side's failed test commands on the
  built merge base and diff the failing FILE SETS — netNew is the PR's own
  failure by measurement (whatever files the diff touches), shared is
  pre-existing by measurement. Counts are never compared: a flaky suite
  fails different test names between runs of the same tree. An unfinished
  base rerun attributes nothing.
- Round ledger: the incremental review cache persists confirmed findings
  under round-scoped ids (R1-2); a high-effort re-review rules on each
  (fixed / still stands / cannot tell) and opens its report with the table,
  the way a human round-2 comment opens with 'M1 is fixed'.
- Three replacement mutation operators in test-efficacy: drop '?? fallback',
  force a comparison-bearing guard condition to 'true', drop a '+ CONST'
  term. Each survivor maps to one crisp untested-protection sentence.
  A line whose raw text and literal-blanked code view disagree yields no
  candidate — an edit index computed across the two views once spliced
  'iftrue 0)' into a guard, and a mangled mutant reads as inconclusive
  while quietly spending a cap slot.
- Quality brief: documentation-parity lens — a new user-facing surface whose
  siblings are documented is a Suggestion naming the sibling precedent; no
  documented sibling, no finding.

* feat(review): render adjudication, workflow step extraction, three verification lenses

Shaped by a live sanitizer-PR verification (QwenLM/qwen-code#8147):

- Render-adjudication capability (opt-in): with QWEN_REVIEW_SCRATCH_REPO
  set, the verifier may post a minimal payload to that user-designated repo
  and rule on GitHub's own rendered HTML — the measured case being an
  @ -> &#64; defusal every local reading called sound while GitHub's real
  renderer registered the mention and fired the notification. Absent the
  setting, rendering claims honestly cap at low confidence / cannot tell.
  Step 7's write ban names the carve-out explicitly.
- qwen review extract-step: lift one workflow step's run: script verbatim
  into an executable, with env (as comments, never half-substituted
  exports), every ${{ }} site listed unevaluated, and a heuristic invoked-
  command list as the stubbing starting point. With base-tree, both arms of
  a by-hand workflow A/B become two invocations. yaml declared as a cli
  dependency (previously resolved only via hoisting).
- Three brief lenses: a borrowed protection idiom missing what made it work
  at home (the code ancestor did the protecting; only the entity was
  copied); a second parser for an authoritatively-parsed format is a
  divergence hunt; tests that pin the mechanism instead of the effect, and
  oracles that mirror the implementation's own model.

* feat(review): sibling-entrance discipline for the fixed verdict

From round 6 of the live sanitizer verification (QwenLM/qwen-code#8147):
the fix closed the fence-shaped entrance into a raw-HTML block, and the
code-span entrance beside it — same divergence, adjacent syntax — stayed
open. A re-check that tests only the reported input rules 'fixed' over a
hole one backtick away.

Both fixed-verdict sites in SKILL Step 6 (the open-Criticals re-check and
the round ledger) now require enumerating a divergence-class defect's
sibling entrances before ruling fixed; a still-open sibling is a NEW
finding, never a reason to withhold the original's fixed — the two
rulings stay separate so the second hole cannot ship unreviewed.

* feat(review): three measured-verification lenses from live rounds 8037/8005

- Threshold-boundary scan: when a fix is a ratio/length guard, hold the
  issue's own variables fixed and binary-search the boundary where the
  behaviour flips; put the number next to what the issue reports. Live
  case: a prose-ratio guard covered the edit/write_file half of its issue
  and silently declined the run_shell_command half (~473-char boundary).
- Delimiter self-injection named as the first parser-differential probe:
  a no-escaping extractor fed its own close tag truncates silently.
- Shared-gate state enumeration: a deliberate-design defence extends only
  to the states it argues — an input-hold argued for 'active' silently
  froze three idle states sharing the same gate. The sibling-entrance
  rule, applied to a state machine instead of a syntax.

* fix(review): address review feedback — false-positive hardening, binary diff guard, error convention (#8215)

* fix(review): address review feedback — base-tree availability gate, test-plan false positives, hunk-probe ranges (#8215)

- base-tree: only stamp a base tree available when runBuildTest actually
  compiled something (ok AND npm toolchain AND a non-empty build). An
  `unsupported` handoff or an empty npm scope returns ok:true having built
  nothing; marking that tree available let an A/B read the absence of a build
  as a behavioural difference.
- cleanup: sweep the stale base-tree build lock a killed builder leaves behind.
- test-plan: read the root manifest's scripts directly so a root-only script
  survives when the root defines no build/test; bail on the inline --root=./dir
  rebasing form; stop treating a positional after an inline --flag=value as the
  flag's value; prefer a failed scoped run when ruling a bare command; anchor
  the npm script alias to a full token so `yarn test:unit` is not truncated.
- test-efficacy: exclude `\ No newline at end of file` from the startLine offset
  count; compute the mutant-overlap range from the header's new-side span so it
  no longer overshoots into a closely following hunk.

* fix(review): address review feedback — diff-header false positives, stale prompt enumeration, added-file hunk probes (#8215)

* fix(review): address review feedback — cd-base exclusion, Test Files count guard, base-tree error handling, probe delegation (#8215)

* fix(review): port the collocated-dropout test to the post-#8050 runner seam

Merging main brought #8050's Windows-portability refactor, which resolves
the probe runner through vitest/package.json's bin — a node_modules/.bin
fake is dead weight it never reads. The 8215-only collocated-dropout test
still installed the old .bin fake, so the REAL vitest ran its fixtures,
price.test.ts genuinely passed, and the hunk scored survived. The test now
overrides the fake package's vitest.mjs like every post-refactor test.

* fix(review): bound the summary rescue, apply the ATX heading rule, sweep stale build locks

The three 8215-layer findings from the latest review, fixed at this
layer (they were first patched further up the stack, where the reviewer
of THIS PR cannot see them):

- trimOutput's summary rescue is capped at 40 lines — uncapped, 40k
  lines of 'Test <n>: …' prose voided the trim entirely (measured 1.6MB
  in, 1.6MB out) and the bounded-output contract is the whole point.
- A '#' with no following whitespace is prose, not a heading (the ATX
  rule GitHub applies): '#8176', '#tag', an unfenced '#!/bin/bash' no
  longer end the Test Plan section mid-body; the bare-#-run crash on
  the closing scan is guarded.
- A base-tree build lock older than 30 minutes is a corpse left by a
  killed builder — swept and rebuilt instead of reporting busy for the
  rest of the review.

* fix(review): EEXIST-only lock busy, bun test alias, chained cd bail, fence backreference

Four live findings from the latest inline review round (the rest of the
round was already fixed upstream by the takeover bot - verified by
probing head behavior rather than re-reading the threads):

- base-tree's lock catch distinguishes EEXIST (a concurrent builder,
  busy) from EPERM/EROFS/ENOSPC (this run's own failure, reported as
  such, not as a busy that will never clear).
- "bun test" is bun's built-in runner, not a package-script alias: it
  runs whether or not any manifest defines test, so ruling it against
  the scripts table filed a false contradicted.
- A chained cd matches the leading-cd shape but the single-hop resolver
  joined file tokens against the FIRST directory; it now bails like the
  exotic-cd case.
- codeSpans' fence regex closes on its own marker via backreference; a
  tilde fence line inside a backtick block ended the span early and
  lines after it were lost to extraction.

* fix(review): close the ten open findings on this PR

- guard-true tested for a comparison anywhere after `if (`, including the
  then-body, so `if (ready) emit(a !== b);` admitted a mutant on the
  comparison-less condition the gate exists to exclude. It now tests the
  condition span only.
- The `survived` detail said "when it changes" for legacy DELETION
  mutants too; it now matches the operator.
- test-delta's `unparsed` required both sides to parse zero files, so a
  PR-side failure whose FAIL lines the trim scattered was silently
  dropped whenever the base rerun happened to parse. netNew/shared come
  from the PR side, so the PR side alone decides.
- failingFilesOf now matches Windows path shapes (backslashes, C:) - a
  missed parse is an unattributed failure, not a loud error.
- The replacement branch of runOneMutant (write-file -> run-probe ->
  classify) had no end-to-end test; one now drives a coalesce operator
  through the real handler and asserts the mutated line, the verdict,
  the operator-specific wording, and that the shared tree is untouched.
- Two tests were vacuous with respect to what they promised: the
  baseline-dir test never asserted the cwd (its helper swallowed the
  argument - fixed at the helper), and the one-candidate-per-line test
  used an input that never triggered the replacement path, so the
  `continue` under test was not load-bearing.
- Reattached the orphaned selectMutants JSDoc; reworded the SKILL line.

* fix(review): the test helper's cwd parameter is required, not optional

CI's `tsc --build` failed on test-delta.test.ts: the exec seam always
passes a cwd, but the helper's signature marked it optional, so pushing
it into a string[] was `string | undefined`.

Missed locally because vitest runs through esbuild, which strips types
without checking them - the suite was green while the build was red. The
gate to run before pushing a type-level change is `npx tsc --build`, not
the test suite.

* fix(review): a base rerun that could not RUN attributes nothing

Two Criticals from the latest review, both reachable on the brief's own
happy path:

- baseUnusable covered only timeouts. Every other way the base side can
  fail to run - an unbuilt base tree, a missing install, a workspace the
  PR ADDED (npm test --workspace cannot resolve on base), an ENOBUFS
  truncation - exits non-zero with zero FAIL lines, which this code read
  as a green base. Every PR-side failure then became netNew: the
  strongest evidence the command emits, manufactured from a base that
  never ran a test. It now attributes nothing and says why.
- Timeout detection was the weaker substring form the sibling explicitly
  rejects; an external SIGTERM (container stop, cancelled job) set
  neither an ETIMEDOUT message nor an exit code and fed straight into
  the above. build-test now exports spawnTimedOut and test-delta asks
  the same question rather than re-deriving it.

Also: the base output is trimmed (it precedes the verdict fields in the
report the agent reads, so an untrimmed megabyte truncates exactly what
the command produces); the guard-true gate no longer reads an arrow
function's => as a comparison (every predicate guard was a candidate -
the if (ready) noise the gate exists to exclude); the term-drop message
no longer calls a string concatenation a reserve term; the unparsed note
describes its own PR-side-only condition; and the ledger's Step 6/Step 8
now agree that a still-standing finding keeps its id.

* fix(review): extract-step resolves all three env/defaults levels, and comments every env line

Two silent-wrongness defects in a command whose whole value is fidelity.

`env:`, `shell:` and `working-directory:` are three-level settings on GitHub —
workflow, job, step, nearest wins — and only the step level appears in the
step's own text. Reading step-level alone reproduced by machine the exact
transcription error this command exists to remove: measured, a step under a
job-level `NODE_ENV: production` and a workflow-level `GLOBAL_FLAG` extracted
with `env: { LOCAL: '1' }` and `workingDirectory: undefined`, so the emitted
script ran with both unset and nothing said so. Not a contrived shape: this
repo carries workflow-level `env:` in 7 workflows, job-level `env:` in 10, and
job-level `defaults.run` in qwen-triage.yml — the workflow the command's own
test plan names. The three levels now merge with the runner's precedence, and
`envSources` records which level each key came from, so an inherited value is
visible rather than indistinguishable from the step's own.

The env block was commented per ENTRY, not per LINE. A YAML block scalar
(qwen-autofix.yml's `SETTINGS_JSON: |-`) reaches the header as a multi-line
string, so its continuation lines landed in command position — and under the
`set -e` the header itself emits, the extracted step died in its own preamble
before its `run:` body ran. Every line is commented now.

Tests pin the effect, not the mechanism: `executableLines()` asserts nothing
but the `run:` body ever reaches command position, plus a `bash -n` parse
check. Verified to flip — all five new assertions fail against the pre-fix
implementation (`{ LOCAL: '1' }`, `undefined` working directory, three
executable lines instead of one, and a real `bash -n` syntax error).

* fix(review): compare failing files by a normalised, project-keyed identity

Critical: the two sides run in DIFFERENT roots (the PR worktree and the
base tree), and netNew/shared compared the parsed paths verbatim - so an
absolute-path runner turned every pre-existing failure into a fabricated
Critical, with the authority of a measurement behind it. Paths are now
normalised against each run's own root (and backslashes to /, so a
Windows path compares with its POSIX-printed twin), which is why
test-delta gained --pr-worktree.

The identity also keeps the vitest project token: dropping it collapsed
same-named test files across workspaces, so a PR-caused failure in one
package could read as pre-existing because another package has a file by
the same name - the worse failure direction.

Also from the same review, all of them reachable on the brief's own path:

- The base rerun now inherits build-test's stdio: ['ignore','pipe','pipe']
  ("a build that asks a question is a build that hangs until the
  deadline") and its trimOutput, which matters because entries[].base
  precedes the verdict fields in the report the agent reads.
- The brief gates on base-tree's `available`, not just its `path`: a tree
  that was created but did not build populates path too, and measuring
  against it turns an infrastructure failure into Criticals.
- A programmatic caller omitting `timeout` no longer sends NaN into
  spawnSync.
- MutantCandidate is a discriminated union, so an operator without its
  replacement line - which would delete a line while reporting "with its
  ?? fallback dropped" - is unrepresentable.
- The comparison class no longer requires a trailing space (if (a<b) is
  the same guard, just unformatted) and matches a brace-less else if.
- DeltaEntry.unparsed's doc now describes the PR-side-only condition it
  actually implements.

* fix(review): restore the whole-command budget, keep generics out of guard-true

Round-1 findings from a fresh review of this PR:

- test-delta had no aggregate deadline: --timeout is PER command and
  defaults to 300s, so three failed commands is 900s against Agent 7's
  600s ceiling - killed with NO report at all, discarding the base-tree
  install and build just paid for. TOTAL_BUDGET_MS mirrors the one
  test-efficacy reserves; commands it cannot fit are disclosed.
- guard-true matched generic calls: `if (isRecord<string>(v))` produced a
  mutant, and a type-guard predicate is exactly the `if (ready)` shape
  whose survivors the gate calls noise. The trailing \s is required, not
  an accidental asymmetry with [!=]== - telling `a<b` from `fn<T>(x)`
  needs a parser, and the gate is silence-biased by design.
- --pr-worktree had no contract test, and its failure mode is the worst
  here: arriving undefined, root stripping silently stops and every
  pre-existing failure becomes a fabricated netNew. The new test feeds
  parseSync's output straight into runTestDelta and asserts an
  attribution only reachable when both roots were stripped (verified
  red against the snake_case field shape that shipped once already).
- Merged the two consecutive doc comments on prWorktree.

* fix(review): a budget-shortened deadline is not the same fact as a slow rerun

Round-2 finding on the budget just restored: `Math.min(perCommandMs,
remaining)` can hand a rerun far less than --timeout, and if it dies
there the note said only "timed out - infrastructure, not evidence".
True, but it sends the reader hunting a hang that is really an exhausted
budget - and unlike a real timeout, a rerun with budget to spare would
still measure it. The note now names those commands separately and says
so. Verified red against removing the tracking line.

* fix(review): brace-tolerant stub list, pipefail fidelity, and extract-step in the briefs

Round-3 findings on this PR, fixed.

`expressionsOf` matched `[^}]*`, so any expression containing a brace —
`format('refs/pull/{0}/head', …)`, `fromJSON('{"a":1}')` — was not mis-listed
but DROPPED. A stub list reads as "these are all the values to supply", so a
silent omission is a value that never gets stubbed. It now scans forward to the
closing `}}`, and reports nothing for an unterminated site rather than
swallowing the rest of the text.

Declaring `shell: bash` is not the runner's default `bash`. The default is
`bash -e {0}`; a declared `bash` (at any level) is
`bash --noprofile --norc -eo pipefail {0}`, and a pipeline whose middle stage
fails aborts under one and not the other. The header now carries `set -eo
pipefail` or `set -e` accordingly — 163 of this repo's 434 `run:` steps are
under a declared bash and were getting the weaker one. A `shell:` value is also
a command template (`perl {0}`), so only its first word goes in the shebang and
the whole template is recorded beside it.

`extract-step` was registered on the CLI and mentioned in DESIGN.md, and
nowhere in SKILL.md or the agent briefs — the runtime prompts. The capability
was unreachable by the agents it was written for. The verifier's brief now
carries it next to the A/B paragraph it composes with, and Step 4 summarises it.

Also: env ordered nearest-first (measured on qwen-autofix.yml:route:0, merge
order put 20 inherited entries ahead of the step's own 26 in a 49-line header);
a valueless `FOO:` renders as the empty string, not `"null"`, and a non-scalar
as JSON rather than `[object Object]`; a missing file no longer reports as a
parse failure; DESIGN.md's lens count matches its list.

The test oracle is rebuilt around the property instead of a filter: the file is
the header plus the body verbatim, and every line before the body is a comment
or a directive the test names. The old helper dropped `set -e` unconditionally,
so it could not tell the header's from one the body legitimately contains — and
would have gone green on a header that leaked exactly that line. 434 real `run:`
steps swept: 0 non-verbatim bodies, 0 live header lines, 0 missed expression
sites, 0 out-of-order env, 0 `bash -n` failures.

* fix(review): restore the replacement sub-cap, stranded on a downstream branch

Round-3 finding, and the third instance of one class: an 8218-layer fix
committed on the 8261 branch, four PRs above the code it belongs to.

Measured over 40 real commits, the replacement operators produce ~24x
the deletion pool (215 vs 9 candidates; guard-true drives it). Every
mutant run drains the same window hunk probes draw from LAST, so
uncapped, most diffs with any replacement candidates leave hunk probing
zero runs - the hunk-survived finding class silently stops firing and
nothing says so. Three slots, and what the sub-cap drops is counted in
skippedForCap rather than lost.

Also swept the other direction: diffed every review file against its
8261 copy to confirm nothing else 8218-layer is stranded up there. The
remaining divergence is 8261's own (the positive control, its lenses).

* docs(review): complete the "delta cannot rule" enumeration in both places

Round-4 finding. The brief and SKILL.md each listed three cases where
test-delta attributes nothing - unparsed, timed-out base, no merge base -
but the code has five: the later rounds of this PR added "a base rerun
that failed without naming any failing file" (it did not measure the
base) and "a command the whole-command budget could not fit". Two
enumerations of the same set with different membership, in the two
places an agent reads.

That is the sibling-enumeration lesson this skill teaches, applied to
its own prose for the second time: the fix is not just adding the
missing members but saying that the report names each case with its own
reason rather than folding them into one.

* fix(review): the invokes list was mostly prose, not commands

Round-4 finding on this PR. `invokes` is documented as a heuristic starting
point, and imprecision is fine — but measured over this repo's 434 real `run:`
steps it was reporting 435 distinct "commands", 267 of them appearing exactly
once, with a worst case of 63 entries made up of words like `CI`, `Evidence`,
`PR` and `and`. A list that size, mostly prose, is not a starting point.

Three causes, each measured:

- A `${{ … }}` expression is not shell, and it routinely contains `||`.
  Splitting on that as a pipeline separator reported both operands as commands
  (`matrix.arch`, `github.event.inputs.version`). Expressions are now masked to
  an opaque token before the split; one sitting in command position contributes
  nothing, which is honest — what it expands to is unknown here by design.
- A heredoc body is input to a command, not a list of them. Its lines were
  scanned as commands, terminator included. 12 steps in this repo carry one.
- The `name=value` skip stepped over the prefix and took the NEXT word as the
  command — but for a quoted value with spaces that word is inside the value:
  `EVIDENCE_SECTION=$'### Evidence images'` reported `Evidence`. Quoted spans
  are now blanked out, with the quote carried across lines so a multi-line
  string's continuation lines are data too. Command substitutions are read
  first, so `body="$(sanitize < "$REPORT")"` still reports `sanitize`.

A `#` preceded by whitespace ends the live part of a line, so an apostrophe in
a trailing comment cannot open a span and eat the rest of the script.

Measured after: 435 distinct commands to 187, singletons 267 to 104, worst case
63 to 27 — and the worst case is now real commands (`awk cat chmod curl git jq
mktemp pkill tar tee timeout`) plus the script's own shell functions. Also
added the builtins a stub could not intercept anyway (`eval`, `exec`, `source`,
`unset`, `command`, …) to the keyword set.

434 steps re-swept: 0 non-verbatim bodies, 0 live header lines, 0 missed
expression sites, 0 out-of-order env, 0 `bash -n` failures.

* docs(review): the rationale named only one of the ways base goes unmeasured

Third and last copy of the enumeration the previous commit fixed. The
bullet's headline already generalised - "base attributes nothing it did
not finish" - but its body named only the timeout, so a reader learning
the contract from the rationale would conclude timeouts are the only
unusable case. Name the set, and say why the report keeps the reasons
apart: "we could not measure" and "we measured nothing" are different
facts to the author, and only one of them is about their PR.

* fix(review): a quoted `<<EOF` is not a heredoc, and a continued line is one command

Round-5 findings, both in the previous commit's own scanner.

A heredoc opener was matched over the whole line, so one inside a string
started heredoc mode: `echo "write <<EOF for a heredoc"` made every later line
wait for a terminator that never arrives. The failure is not a missing entry
but a missing REST — measured, a three-line script returned `[]` instead of
`[curl, jq]`, empty and entirely plausible. Opener detection moved inside the
quote walk, where it only fires outside quotes; the quoted forms (`<<'EOF'`)
are consumed by the match, so their quotes never open a span either.

A backslash-continued command was scanned as several lines, which puts the next
ARGUMENT in command position — this is where `apt-get install -y \` /
`  libx11-dev` reported the package as an invoked command. Continuations are
now joined into one logical line before scanning.

Measured after: 187 distinct commands to 185, and the singleton tail is now
dominated by real ones — PowerShell cmdlets, macOS tooling, and the scripts'
own shell functions. 434 real steps re-swept: 0 non-verbatim bodies, 0 live
header lines, 0 missed expression sites, 0 out-of-order env, 0 `bash -n`
failures.

* fix(review): case labels, a second heredoc, and an expression in command position

Round-6 findings, from running the scanner against adversarial shapes rather
than reading it again. Two are UNDER-reports, which is the worse direction: a
command missing from the list is a stub the verifier never writes, so the
extraction reaches the real network.

- A `case` pattern label stopped the scan on its own line: `blocked) gh api x`
  reported nothing, losing `gh`. The label is now stepped over like a `name=`
  prefix.
- Only the first heredoc opener on a line was tracked, so `cat <<A <<B` left
  the second body and its terminator read as commands (`B`, `y`). Openers are
  queued and consumed in order.
- Masking an expression to a QUOTED token let the quote-stripper delete it
  entirely, so `${{ steps.x.outputs.cmd }} arg` reported `arg` as the command.
  The token now survives stripping and cannot match a command word, so an
  expression in command position contributes nothing.

Ten further adversarial shapes were already correct and are pinned as
regression guards: nested `$( )` in quotes, subshells, function definitions,
indented heredoc terminators, backticks, bare redirects, adjacent and empty
`${{ }}` sites, and a JSON literal inside an expression. Verified to flip —
exactly the three above fail against the previous commit, the other ten pass.

434 real steps re-swept: 0 non-verbatim bodies, 0 live header lines, 0 missed
expression sites, 0 out-of-order env, 0 `bash -n` failures.

* fix(review): annotate the continuation accumulator so tsc can type it

`invokedCommandsOf`'s backslash-continuation loop failed to compile with
TS7022: the narrowed type of `pending` at the join line is the union of
the loop-entry value and the back edge, and the back edge is computed
from the join itself. The declaration's own annotation does not break
that cycle - control-flow narrowing runs after it - so the checker gives
up and calls the result `any`.

Caught only on a forced rebuild. `tsc --build` had been reporting this
tree clean off a stale .tsbuildinfo, which is the same shape of gap that
put a type error into CI last week: the test runner strips types, so the
suite stayed green either way. Gate with --force.

* fix(review): a file-count label stops counting at the end of its line

`Test Files  45 passed` filing its 45 as a differing TEST count was
fixed once, with a lookbehind on the bare-count pattern. That only ever
rejected the all-green shape. The moment any file fails, the runner
prints `Test Files  1 failed | 44 passed (45)`, the label is no longer
adjacent to the number, and 44 comes through as a test count - so the
note reads "claimed 44, observed 1323" on exactly the runs whose summary
someone would paste. Adjacency was the wrong invariant; the line is.

Masking from the label to end-of-line is distance-independent and picks
up jest's `Test Suites: 1 failed, 44 passed, 45 total` at no cost. The
label keeps its `Test` word on purpose: a first cut matched a bare
`files` and blanked the line in "expect all four files and 471 tests to
pass", silencing a real claim. An existing test caught that, which is
the argument for the rule being as narrow as it is - anything that
suppresses claims is worth exactly its narrowness.

Also drops the now-dead lookbehind, which a reviewer had separately (and
wrongly) called a JS syntax error; variable-length lookbehind is legal
in V8, and the module parsed fine. It goes because the line rule
subsumes it, not because it was broken.

* fix(review): parse the base rerun before its output is trimmed

`trimOutput` rescues module errors and runner summaries out of the
omitted middle, not the per-file FAIL lines this command reads. A base
suite whose failure section overruns the tail budget therefore lost
failing files into the gap - and a SHORT base set is the dangerous
direction, because netNew is the PR side minus the base side. Every file
the trim hid came back as a Critical attributed to this PR by
"measurement". Parse the raw text, report the bounded one.

The PR side cannot be fixed here: it is read out of build-test's stored
output, trimmed before this command existed. That loss runs the other
way - it understates `shared`, never invents a netNew - so the entry
carries `prTruncated` and the note says the list may be partial. A
silence-biased gap is still a gap the author should hear about.

Also names both selection caps in the mutant-skip diagnostic. The count
accumulates replacement sub-cap drops, and with 2 deletions and 6
replacements the total is exactly MAX_MUTANTS: the main cap never fires,
yet 3 are dropped, and the message sent the reader looking for a pool of
11 candidates that never existed.

* test(review): pin the `sh` set-line to exactly what the runner uses

A reviewer asked for `expect(script).not.toContain('set -e')` on a
`shell: sh` step. The opposite is correct - GitHub runs that step as
`sh -e {0}` - but the thread was right that nothing pinned it either
way. Assert both halves: `set -e` is present, `pipefail` is not. Dropping
the line makes an extracted `sh` step run past a failure the runner
stops on; adding pipefail claims a bash feature `sh` does not have.

* fix(review): rerun only the command shapes build-test emits

This command reads a report off disk and then executes the strings in it
with `shell: true`, in the base worktree. Nothing else in the pipeline
re-executes a value it read back from a file, so nothing else has to
care where that value came from - this does, and the provenance is worse
than it looks: the command is `npm test --workspace="<dir>"`, the
workspace token is a directory, and a directory is a name a pull request
chooses. `packages/x";curl …|sh;"` is a legal path in git and on Linux
and it round-trips through the report into a shell.

Restricting to the emitter's own grammar costs nothing real, since that
grammar is the two shapes build-test produces. A command outside it is
skipped and disclosed, the same treatment everything else this command
cannot do already gets, so a future shape degrades to "judge it by the
diff" rather than to arbitrary execution.

* fix(review): the working directory is a setting the extraction was losing

Two findings, both of them this file failing its own stated argument.

The stub list dropped it. `expressions` covered the script and the env
and nothing else, so a `working-directory: ${{ github.workspace }}/x`
produced an empty list and a summary line reading "0 ${{ }} site(s) to
stub" - and `expressionsOf`'s own comment says why that is the failure
this list cannot afford: the caller reads it as "these are all the
values to supply". Widened to every setting the command carries, the
`shell:` template included.

The emitted script never mentioned it. The env block is commented into
the header precisely so a reader of the script alone can see it; the
working directory changes what the script does just as much and was in
the metadata only. The argument for reading all three levels, written in
this file, is that a step run "in the wrong directory, and nothing says
so" is the transcription error the command exists to remove - which is
exactly what the header did. It is a comment rather than a `cd` for the
same reason env is comments, not exports: the value may hold `${{ … }}`
and this command substitutes nothing.

Both pinned, and both checked by deleting the fix: each mutation fails
exactly one of the two new tests.

* fix(review): $(( )) is arithmetic, and a heredoc's form decides where it ends

Three fixes and one deliberate non-fix, all measured against this repo's
own 434 `run:` steps rather than argued.

`$(( ))` was read as a command substitution, so `N=$((N + 1))` reported
`N` as a command to stub. It was the single largest source of junk in
the list: 196 distinct "commands" across the corpus, 165 without it.

A plain `<<WORD` heredoc ends only on a line that is exactly WORD; the
loose match ended it on an indented `EOF` inside the body and then read
the body as commands, which is how `rm` got reported for a script that
never runs it. `<<-` stays looser than bash (any leading whitespace, not
just tabs) because looser can only end a body early, and this file's
priority is that an under-report is the worse direction.

`[^()]*` matched only the innermost `$( )`, so `X=$(gh api $(u))` lost
`gh` - a missed stub, and the extraction reaches the network. Depth
counted now, and the assignment-prefix skip no longer steps over an
unclosed `X=$(gh` into reading `api` as the command.

The non-fix is recorded where the next reader will hit it: the quote
walk is flat while shell quoting nests, and over ~300 lines the drift
reports fragments of jq source as commands. Inserting a separator where
a blanked span was removes nine of those, but it splits `a"X"b`, which
is one word to the shell, and the minimal reproducer for the difference
is 296 lines - nothing short enough to pin it. A scanner nobody can pin
costs more than the junk it removes.

* fix(review): refuse an ambiguous step name, and report errors like the siblings

Two findings, both about a message the caller never gets.

A job may legally hold two steps with the same name. The selector took
the first and said nothing, which is the failure this file's own header
names - "picks the same-named step from the wrong job" - and it is worst
in the use the command exists for: A/B extraction runs it once per tree,
so a PR that adds or reorders a duplicate leaves the two sides comparing
different steps while reporting on one. Refused out loud now, naming the
indices; the index is always available and never ambiguous.

The handler also let every throw propagate, so five carefully separated
messages - cannot read vs cannot parse vs no job vs no step vs no `run:`
- all arrived as "An unexpected critical error occurred" under a stack
trace. `base-tree` and `test-plan` in the same directory already catch,
write the message, and set exit 1. Matched. The separation between "the
path is wrong" and "the YAML is wrong" only pays if the caller sees it.

* docs(review): the brief's list of limits was one short

"Two limits worth knowing before you spend the step" became three when
the selector started refusing an ambiguous step name, and the count went
stale in the same commit that made it wrong. This is the enumeration
drift the skill teaches, in the text that teaches it.

The added entry says what to do rather than only what happens: pass the
index, which is what an A/B wants regardless — the two trees have to
select the same step, and a name that moved between them is exactly how
they stop doing that.

* chore(review): drop a scratch probe file that reached the branch

`packages/cli/inert.mjs` was a throwaway harness for sweeping the repo's
own workflows; its cleanup ran with a relative path from the wrong
directory and it got committed by the next `git add -A`.

It failed CI and not the local hook because the two lint different sets:
lint-staged filters to `*.{js,jsx,ts,tsx}`, which does not include
`.mjs`, while CI's flat config picks the file up regardless of `--ext`.
Verified with CI's own command this time, not the hook's.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-08-01 15:13:10 +00:00
jinye
1d1bc49473
fix(external-context): harden MCP dependencies (#8206)
* fix(external-context): harden MCP dependencies

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

* fix(external-context): drop no-op MCP override, raise SDK floors (#8206)

Remove the `@modelcontextprotocol/sdk@^1.30.0` -> `@hono/node-server`
override: resolution with and without it is byte-identical because the
SDK's own `^1.19.9 || ^2.0.5` range already selects 2.0.12, and
mobile-mcp's exact SDK pin (not this entry) keeps its nested Hono 1.

Raise the `@modelcontextprotocol/sdk` floor to `^1.30.0` on the packages
that actually instantiate the Hono-backed transport (cli, core,
sdk-typescript, vscode-ide-companion) so the hardened constraint lives in
the manifests where the code runs, matching the PR's stated intent.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-01 01:04:16 +00:00
qwen-code-ci-bot
0d3473cb4d
chore(release): v0.21.2 (#8200)
* chore(release): v0.21.2

* docs(changelog): sync for v0.21.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-31 05:22:46 +00:00
OrbitZore
ec9c36ef82
feat(channels): add GitLab polling channel adapter (#7862)
* feat(channels): add GitLab polling channel adapter

Poll GitLab todos via @gitbeaker/rest, dispatch notes through the
existing PollingChannelBase pipeline. Key design points:

- action_prompt_template config drives event filtering and metadata
  rendering (unconfigured actions are skipped)
- Per-repo cursor (repo[chatId].last_read) as notes window lower bound,
  global lastProcessedAt for todo-level dedup
- mark_done after successful processing; failure skips mark_done for
  retry on next poll
- Mention gating delegated to base GroupGate (adapter only sets
  isMentioned flag)
- First-contact body fallback for todos with no notes (e.g. mention in
  issue description)

* fix(channels/gitlab): persist cursor after each successful todo

Call saveCursor() immediately after advancing lastProcessedAt so that
progress is durable even if the process crashes mid-poll. Also removes
the local watermark variable in favor of direct assignment.

* fix(channels/gitlab): persist cursor on every advancement including skips

* fix(channels/gitlab): address review critical issues

- Remove non-functional proxyAgent (gitbeaker doesn't support it)
- Construct repo_url from host + path (API doesn't return web_url)
- Handle directly_addressed action (falls back to mentioned template)
- First-contact fetches target description instead of using todo.body
- Move todo.project dereference inside try block
- Filter confidential notes
- Update channel-registry.test.ts for gitlab entry

* fix(channels/gitlab): address review suggestions

- Warn on connect if action_prompt_template is not configured
- Guard todo.target.iid before use
- Skip paths now mark_done (best-effort) to clean GitLab UI
- Remove postErrorComment (avoids duplicate comments on retry)
- Fetch only first page of notes (desc, maxPages:1, perPage:100)
  instead of paginating entire note history
- Extract fetchRecentNotes for single-page windowed enumeration

* refactor(channels/gitlab): simplify to todo.body dispatch, add description mention support

- Remove notes API fetching; dispatch todo.body directly
- Detect description mentions via target_url anchor (#note_ absence)
- Always fetch target description for %description% metadata
- Remove per-repo cursor; dedup via cursor + mark_done only
- Cursor advances regardless of success/failure (no retry)
- Use zod for cursor validation
- Rename template vars to GitLab terminology:
  %project% %project_url% %target_type% %iid% %title% %description% %todo_id%
- Support %% escape for literal percent

* docs(channels): add GitLab adapter documentation

- New user guide: docs/users/features/channels/gitlab.md
- Update _meta.ts navigation
- Update developer adapter matrix and SDK list

* fix(channels/gitlab): use correct Issues.show(issueIid, { projectId }) signature

* chore: regenerate NOTICES.txt for new gitlab channel dependencies

* fix(channels/gitlab): address review suggestions

- Add todo.project null guard (item 2)
- Single-pass regex for %% escape + %var% substitution (item 4)
- sendThreadMessage throws directly on undefined threadId (item 5)
- Dedup fetchDescription with per-poll cache (item 6)
- Remove per-todo saveCursor; base class saves after pollOnce (item 7)
- Add undefined threadId test (item 8)
- Expand confidential notes limitation in docs (item 3)

* test(channels/gitlab): add mention tests, directly_addressed coverage, skip assertions, temp cleanup

- New mention.test.ts: 14 cases for testBotMention/stripBotMention/escapeRegex
- Add directly_addressed fallback test
- Skip tests now assert TodoLists.done + cursor advancement
- afterEach cleans up mkdtempSync temp dirs

* fix(channels/gitlab): address review round 4

- Non-mention actions (assigned, etc.) set forceMentioned=true to bypass GroupGate
- Merge dead note-filter tests into single 'skips todo authored by bot'
- Log fetchDescription errors to stderr instead of silent swallow
- Post error comment on issue/MR when handleInbound fails (best-effort)

* fix(channels/gitlab): always force isMentioned=true, remove regex re-derivation

The action_prompt_template config is already the event filter, and
GitLab has already decided the mention when creating the todo.
Re-deriving isMentioned via regex on todo.body causes permanent
message loss when the regex misses (description mention + fetch
failure, group mentions). Always set forceMentioned=true so
GroupGate never drops a todo that passed the template filter.

* fix(channels/gitlab): propagate fetchDescription errors for description mentions

For note mentions, description is metadata-only — fetch failure is
logged and swallowed. For description mentions, description IS the
message — fetch failure now propagates to the outer catch, which
posts the ⚠️ error comment so the user knows to re-mention.

* perf(channels/gitlab): clean up stale todos, skip unnecessary fetchDescription

- Mark stale todos (updated_at <= cursor) as done on each poll to
  prevent perpetual re-fetching of pre-existing pending todos
- Skip fetchDescription for note mentions when template does not
  contain %description%, saving one API call per todo
- Update docs: stale todo cleanup, error comment on failure

* docs(channels/gitlab): clarify requireMention is bypassed, template is the real filter

* Apply suggestions from code review

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(channels/gitlab): use todo ID cursor instead of timestamp to eliminate equal-timestamp loss

Timestamp-based cursors (second granularity) could silently destroy
todos sharing the same updated_at as the cursor boundary. Switch to
monotonically increasing todo IDs which are unique and collision-free.

Add initialized flag to preserve first-start drain semantics: pre-existing
pending todos are marked done without dispatch on the first poll cycle.

* fix(channels/gitlab): harden first-poll drain, add ordering tests, fix lockfile

- Replace Math.max(...spread) with reduce to avoid RangeError on large
  backlogs (~100k+ todos). Move initialized=true after the drain work so
  any throw retries the drain instead of falling through to dispatch.
- Add unit tests: identical-timestamp delivery and id-order-when-updated_at-disagrees
  (kills M2 sort mutant).
- Align lockfile: file:../base → ^0.21.0 for channel-base dep.

* fix(channels/gitlab): include dot in mention lookahead for GitLab usernames

GitLab usernames may contain dots (e.g. bot.name). The lookahead
character class inherited from GitHub omitted '.', causing @bot.name
to match as @bot. Add '.' to the negated class.

* docs(channels/gitlab): align docs with ID cursor and drain semantics

- Add first-poll drain as step 2 in How It Works
- Clarify GroupGate always passes (isMentioned forced true)
- Document initialized flag in Known Limitations

* Apply suggestions from code review

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>

* fix(channels/gitlab): align package version and channel-base dependency to 0.21.1

Bump version from 0.21.0 to 0.21.1 to match other channel packages after
upstream merge. Pin @qwen-code/channel-base to exact 0.21.1 instead of
^0.21.0, matching the convention used by other published channels.

* fix(channels/gitlab): regenerate lockfile to match package.json versions

Manually add only gitlab-related lockfile entries (workspace, @gitbeaker
packages, transitive deps, channel-gitlab link) without unrelated npm
normalization churn.

* test(channels/gitlab): add regression tests for first-poll drain hardening

Two tests that kill the M1 (Math.max spread RangeError) and M2 (flag
ordering) mutants which survived the original 46-test suite:

- 150k todo drain verifies reduce() handles large backlogs without
  RangeError and without dispatching
- Drain throw verifies initialized stays false so the next poll retries
  the drain instead of falling through to dispatch

Test file duration: ~40ms → ~170ms.

* docs(channels/gitlab): clarify groupPolicy must be "open" and add runtime warning

The default groupPolicy "disabled" silently drops all mentions — todos are
marked done and cursor advances, but no dispatch occurs. Fix misleading docs
that said "GroupGate always passes" (only true at groupPolicy: "open") and
add a connect()-time warning when groupPolicy is not "open".

* fix(channels/gitlab): correct xcase integrity hash in lockfile

The manually added xcase entry had a typo in the sha512 hash (ys → ks),
causing npm ci EINTEGRITY failures in CI.

* fix(channels/gitlab): correct requester-utils integrity hash in lockfile

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

* fix(channels/gitlab): allow groupPolicy "allowlist" in warning and docs

The groupPolicy warning and docs incorrectly stated that groupPolicy
must be "open". In reality "allowlist" with the project listed also
works because isMentioned is forced true and GroupGate only requires
the group to be listed. Also fix the inaccurate "no error is logged"
claim — ChannelBase logs preflight rejected reason=group_disabled.

Fixes R5-🟡3 from PR #7862 review.

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-29 14:28:31 +00:00
qwen-code-ci-bot
3144046c6b
chore(release): v0.21.1 (#7958)
* chore(release): v0.21.1

* docs(changelog): sync for v0.21.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-29 00:25:30 +00:00
jinye
9bdc62c74b
perf(cli): replace comment-json settings parser (#7747)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-26 14:42:51 +00:00
OrbitZore
62e009a952
feat(channels): GitHub polling adapter with notification-as-wakeup architecture (#7632)
* feat(channels): add GitHub polling adapter with notification-as-wakeup architecture

Introduce a GitHub channel adapter that monitors notifications and
responds to @mentions on issues/PRs by posting comments. Uses
last_read_at as a per-thread watermark for comment enumeration,
replacing the unreliable latest_comment_url approach.

Foundation changes to ChannelBase:
- sendThreadMessage for thread-targeted delivery (IM adapters unchanged)
- Envelope.metadata appended to prompt after command parsing
- chat_thread session scope (channel:chatId:threadId) prevents
  cross-repo session collision
- polling-helpers: testBotMention/stripBotMention (separate detection
  from stripping, no whitespace collapsing), cursor persistence,
  abortableSleep

GitHub adapter design:
- Notifications as wake-up signals only (unread filtering)
- listComments enumeration with last_read_at watermark
- Bot self-comment filtering, case-insensitive mention regex
- In-memory recentlyProcessed set for mark-read failure dedup
- First-contact: new issue body @bot triggers processing
- Error comment + cursor advance on handleInbound failure
- pollInterval minimum 60s, exponential backoff 2s-30s

* refactor(channels): extract PollingChannelBase from polling-helpers

Replace the loose polling-helpers module with a PollingChannelBase<Cursor>
abstract class that encapsulates the poll loop, cursor persistence (JSON,
atomic write), exponential backoff, and start/stop lifecycle. Subclasses
implement only pollOnce() and createInitialCursor().

- Delete polling-helpers.ts (cursor fns + abortableSleep moved into base)
- Move mention utilities (testBotMention/stripBotMention) to github pkg
- GithubAdapter now extends PollingChannelBase<{ lastProcessedAt }>

* fix(channels): remove Gitea/GitLab mention from sendThreadMessage JSDoc

* fix(channels): match /pulls/N in notification subject URL

GitHub PR notifications use /repos/{owner}/{repo}/pulls/{N} in
subject.url, not /issues/{N}. The regex only matched /issues/,
causing PR notifications to be skipped and marked read.

Also sets threadId to 'pr:N' for PRs (was always 'issue:N').

* test(channels): add PR body first-contact unit test

Verify that PR notifications with @mention in the body (not a comment)
correctly trigger the first-contact path: extractFromSubjectUrl matches
/pulls/N, listComments returns empty, tryFirstContactBody fetches the
PR body and dispatches to handleInbound with threadId 'pr:N'.

* feat(channels): read pollInterval from channel config in PollingChannelBase

Move pollInterval config reading from GithubAdapter to the base class.
The user's configured pollInterval in settings.json is now respected
directly without a minimum enforcement. Defaults to 60000ms when not
configured.

* fix(channels): prepend metadata before prompt text

Agent sees issue/PR context (type, title, URL) before the user's
request, improving comprehension. Metadata is still appended after
slash-command parsing so commands are not affected.

* refactor(channels): route all ChannelBase delivery through sendThreadMessage

Replace all internal sendMessage calls with sendThreadMessage, passing
envelope.threadId (or target.threadId / undefined) so polling adapters
can deliver to the correct thread. IM adapters are unaffected — the
default sendThreadMessage falls through to sendMessage.

* docs(channels): document sendThreadMessage delivery architecture

* fix(channels): address review findings

- Cap recentlyProcessed Set at 10k entries to prevent unbounded growth
- Validate cursor JSON shape (non-null object) in loadCursorFromDisk
- sendThreadMessage falls through to sendMessage when threadId is
  undefined instead of silently dropping
- Remove duplicate pollInterval from GithubConfig (now in ChannelConfig)
- Fix chat_thread routing key trailing colon when threadId is undefined

* docs(channels): fix metadata JSDoc — prepended, not appended

* fix(channels): use recentlyProcessed dedup for first-contact body

Replace the fragile createdAt-vs-cursor check in tryFirstContactBody
with the recentlyProcessed set. The cursor advances globally based on
notification updated_at — when a different notification with a later
updated_at is processed first, the cursor can advance past the issue's
created_at, causing the first-contact check to incorrectly skip the
issue body (forget reply bug, found in E2E TC-2b).

* refactor(channels): two-layer dedup for GitHub adapter

Layer 1: global cursor filters notifications by updated_at (sorted
ascending, old first). Layer 2: server-side last_read_at filters
comments by created_at (sorted ascending).

- Delete recentlyProcessed Set (no longer needed)
- Sort notifications by updated_at ascending before processing
- Sort comments by created_at ascending before processing
- Pass latest comment created_at to markThreadAsRead as last_read_at

* fix(channels): address review findings on GitHub adapter

Blockers:
- sessionScope: add defaultSessionScope to ChannelPlugin, apply in
  parseChannelConfig so router and adapter agree on 'chat_thread'
- channel-registry.test.ts: add 'github' to expected type list

Should-fix:
- Replace per-thread markThreadAsRead (PATCH) with bulk
  markNotificationsAsRead (PUT /notifications + last_read_at).
  API errors stop the batch without marking failed notifications
  read; handleInbound errors still advance (error comment posted).
- connect() throws on bot identity failure instead of failing open
- metadata appended after promptText (inside sender attribution)
- isSharedSessionTarget includes 'chat_thread' scope

Nits:
- startPollLoop re-entrancy guard
- clean-package-build-artifacts.js includes github
- index.ts re-exports GithubChannel

* fix(channels): use max updated_at of all fetched notifications as last_read_at

Prevents re-fetching the same notifications in the next poll cycle.
The bulk PUT /notifications marks all fetched notifications as read
up to the max updated_at, regardless of per-notification success.

* fix(channels): address review round 2 findings

- #12: loadCursorFromDisk rejects arrays
- #13: pollInterval validates positive finite number
- #19: first-contact gate uses dispatchedMention flag (not newComments.length)
- #25: stripBotMention no longer trims (preserves indentation)
- #27: remove adapter-level requireMention, unify on GroupGate
- #31: add chat_thread SessionRouter routing key tests
- #33: clear metadata on collect-mode synthetic envelope
- #35: fix PollingChannelBase.test import path
- #36: add @octokit/rest to 15-channel-adapters.md dependencies

* docs(channels): document known limitations for GitHub adapter

- First start skips existing unread notifications (cursor = now)
- Requires classic PAT (fine-grained PATs lack notifications API)
- PR review comments not enumerated (issue comments only)

* fix(channels): address review round 3 findings

- #9: buildMetadata derives web URL from baseUrl (GHE support)
- #12: sendThreadMessage throws on invalid threadId format
- #19: mention lookbehind matches cc:@bot and "@bot" patterns
- #23: cursor file name uses sha256 hash to prevent collision
- #26: test verifies cursor persistence to disk
- #31: postErrorComment double-failure logs to stderr
- #45: tests use mkdtempSync isolation instead of real QWEN_HOME

* fix(channels): pass threadId through pairing flow + sendResponseMessage test

- #13+16: onPairingRequired receives envelope.threadId and passes it
  to sendThreadMessage, so pairing codes are delivered on threaded
  channels (GitHub) instead of throwing
- #6: add test verifying sendResponseMessage resolves threadId from
  router.getTarget and passes it to sendThreadMessage

* fix(channels): pass proxy to Octokit for daemon-worker environments

- #44: read this.proxy from ChannelBaseOptions and pass
  HttpsProxyAgent to Octokit request.agent, matching the
  Telegram adapter pattern

* fix(channels): address review findings — immutable senderId, comment time window, validateCursor, retry wrapper

- senderId uses immutable user.id; allowedUsers resolved to IDs at connect
- Comment filter upper bound: updated_at <= maxUpdatedAt (batch window)
- Per-notification errors use continue (best-effort), not break
- validateCursor() virtual hook for subclass cursor shape validation
- sendThreadMessage/postErrorComment wrapped in githubApi() retry
- webOrigin handles default api.github.com → github.com
- Docs: classic PAT only, markNotificationsAsRead, dedup claims removed
- Tests: threadId priority, metadata consumption, defaultSessionScope,
  QWEN_HOME isolation, persistent mock rejection

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

* fix(channels): mark notifications read before processing to prevent duplicate replies

Bot's own replies bump notification updated_at past the pre-captured
maxUpdatedAt, so markNotificationsAsRead(maxUpdatedAt) failed to mark
them read — the next poll re-fetched the same comments and replied
again.

Move markNotificationsAsRead + cursor advance before the processing
loop (best-effort delivery). This is safe because bot's own comments
do not flip notifications back to unread. Update docs to reflect the
new poll cycle order and best-effort semantics.

* fix(channels): update sender gate after allowedUser ID resolution and harden tests

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

* fix(channels): cursor-based comment window to prevent duplicate replies

PUT /notifications is async (202) with a last_read_at cutoff — the
bot's reply bumps updated_at past the cutoff before the server
processes the mark, so the notification is never marked read and gets
re-fetched on the next poll, causing duplicate replies.

Use the cursor value before advancement as an exclusive lower bound
for the comment enumeration window: (windowSince, maxUpdatedAt].
Comments already eligible in a previous poll are excluded regardless
of whether the mark succeeded. Zero new persistent state.

* fix(channels): cursor-based comment window to prevent duplicate replies

PUT /notifications is async (202) with a last_read_at cutoff — the
bot's reply bumps updated_at past the cutoff before the server
processes the mark, so the notification is never marked read and gets
re-fetched on the next poll, causing duplicate replies.

Use the cursor value before advancement as an exclusive lower bound
for the comment enumeration window, with per-notification last_read_at
as the preferred lower bound when available (server-side per-thread
watermark). Comments already eligible in a previous poll are excluded
regardless of whether the mark succeeded. Zero new persistent state.

* fix(channels): address review findings — null guard, cursor validation, metadata dedup, abortable sleep, docs

- Guard against null notification.subject.url in pollOnce
- Validate lastProcessedAt is a parseable date in validateCursor
- Add metadata: undefined to second collect-mode drain path
- Refactor abortableSleep as protected method on PollingChannelBase
- Fix docs: requireMention is nested under groups.*
- Add tests: chat_thread shared session, dispatchedBodies eviction,
  cursor enumeration window, last_read_at in mention tests

* docs(channels): sync docs with implementation — cursor shape, error handling, GitHub adapter tables, first-contact

- Design doc: update Cursor to { lastProcessedAt, dispatchedBodies? }, add
  validateCursor date check, abortableSleep protected method, break-on-error
  semantics, subject.url null guard
- Developer docs: add GitHub to adapter table and adapter matrix
- User guide: add first-contact step to How It Works, clarify mark-before-process

* fix(channels): address review round 2 — error dedup, abortable retry, backoff reset, window test

- Record dispatchedBody on first-contact handleInbound failure to prevent
  duplicate error comments when mark-read async hasn't taken effect
- Use abortableSleep instead of raw setTimeout in githubApi retry so
  disconnect() can interrupt rate-limit cooldowns
- Reset consecutiveErrors in startPollLoop so stop/restart cycles don't
  inherit stale elevated backoff
- Add test for cursor window client-side lower-bound exclusion filter

* fix(channels): address review round 3 — cursor validation, error dedup, sender gate, bot-self body

- validateCursor: normalize falsy non-array dispatchedBodies (false/0/""/null)
  to [] instead of passing them through to .includes() which throws TypeError
- Set dispatchedMention after postErrorComment to prevent first-contact from
  posting a duplicate error comment on the same thread
- Only set dispatchedMention when the sender passes the sender gate, so a
  disallowed commenter's mention no longer suppresses a valid first-contact
  body from an allowed issue author
- Skip bot-authored issue bodies in tryFirstContactBody to prevent
  self-response loops under open sender policy

* fix(channels): address review suggestions — test coverage, cursor filename, assertion precision

- Pairing flow: add threadId pass-through regression test
- pollInterval: add table-driven edge cases (0, -1, NaN, Infinity, string)
- Add null-URL notification followed by valid notification batch test
- Fix comment window test to assert paginate call 3 (listComments) not call 2
- Truncate cursor filename encoded prefix to 200 chars (filesystem 255 limit)
- Assert mark-read uses batch maxUpdatedAt, not just { read: true }
- Assert real GitHub plugin declares defaultSessionScope chat_thread
- Add invocationCallOrder assertion for mark-before-process ordering

* fix(channels): address review round 4 — allowedUsers throw on resolve failure, crash table fix, mark-read failure test

* fix(channels): address review round 5 — created_at filter, retry-after NaN guard, retry/sendThreadMessage tests, docs fixes

* fix(channels): address ci-bot review 4778587403 — reconnect idempotency, github type enumerations, retry/webOrigin tests

* chore(channels): align channel-github version to 0.21.0 after upstream merge

* chore(channels): update package-lock.json for channel-github 0.21.0

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

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: OrbitZore <orbitzore@users.noreply.github.com>
2026-07-25 09:31:50 +00:00
qwen-code-ci-bot
9e7acec863
chore(release): v0.21.0 (#7675)
* chore(release): v0.21.0

* docs(changelog): sync for v0.21.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-24 13:47:34 +00:00
Damian Tometzki
3b3a593600
Fix(cli): use npm view for update check instead of update-notifier (#7515) (#7528)
* fix(cli): use npm view for update check instead of update-notifier (#7515)

* fix(cli): use npm view for update check instead of update-notifier (#7515)

* fix(cli): accept array-wrapped npm view output in update check (#7515)

npm 11+ prints `npm view <pkg> dist-tags.<tag> --json` as ["0.20.1"]
instead of "0.20.1", so the strict string check re-broke the update
check with "Invalid npm latest version response". Accept both shapes.

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

* chore(cli): drop update-notifier dependency and dead install-detection code (#7515)

Version checking now goes through npm view for every install type, so:
- replace the update-notifier UpdateInfo type import with a local interface
  and remove update-notifier / @types/update-notifier from dependencies
- remove isGlobalNpmInstallation and looksLikeNpmPackagePath, which no
  longer have any production callers, along with their tests

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

* docs(cli): fix npm version in array-output comment (npm 12+, not 11+)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-23 08:36:35 +00:00
qwen-code-ci-bot
97a834bc0d
chore(release): v0.20.1 (#7461)
* chore(release): v0.20.1

* docs(changelog): sync for v0.20.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-22 02:00:22 +00:00
qwen-code-ci-bot
bc0e2cd180
chore(release): v0.20.0 (#7211)
* chore(release): v0.20.0

* docs(changelog): sync for v0.20.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-19 07:35:40 +00:00
qwen-code-ci-bot
3ede6261ea
chore(release): v0.19.12 (#7176)
* chore(release): v0.19.12

* docs(changelog): sync for v0.19.12

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-18 14:58:42 +00:00
ytahdn
0ecba4b3c7
feat(web-shell): add skill management pages (#7018)
* feat(web-shell): add skill management pages

* fix(cli): inject GitHub token for skill installs

* test(integration): include skill management capability

* fix(cli): harden skill installation failures

* fix(skills): preserve management compatibility

* fix(cli): isolate skill install transactions

* fix(cli): address skill install review findings

---------

Co-authored-by: ytahdn <ytahdn@gmail.com>
2026-07-17 06:42:46 +00:00
qwen-code-ci-bot
bbec6dffb9
chore(release): v0.19.11 (#7042)
* chore(release): v0.19.11

* docs(changelog): sync for v0.19.11

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-16 15:23:33 +00:00
qwen-code-ci-bot
42d7d28d11
chore(release): v0.19.10 (#6855)
* chore(release): v0.19.10

* docs(changelog): sync for v0.19.10

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-14 05:12:22 +00:00
jinye
51d4ce48db
feat(serve): persist dynamic workspace registrations (#6716)
* feat(serve): persist dynamic workspace registrations

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

* codex: address PR review feedback (#6716)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-11 16:49:40 +00:00
qwen-code-ci-bot
40ed6b21d7
chore(release): v0.19.9 (#6693)
* chore(release): v0.19.9

* docs(changelog): sync for v0.19.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-11 00:35:15 +00:00
tanzhenxin
7a9ee09f49
fix(core): honor NO_PROXY for model requests (#6640) 2026-07-10 10:41:04 +00:00
qwen-code-ci-bot
b330ec884f
chore(release): v0.19.8 (#6549)
* chore(release): v0.19.8

* docs(changelog): sync for v0.19.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-08 15:51:03 +00:00
qwen-code-ci-bot
86ae16a6d6
chore(release): v0.19.7 (#6484)
* chore(release): v0.19.7

* docs(changelog): sync for v0.19.7

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-07 17:25:48 +00:00
qqqys
467b292b50
feat(channels): add WeCom intelligent robot channel (#6436)
* feat(channels): add WeCom smart bot channel

* fix(channels): harden wecom review suggestions

* fix(channels): address wecom critical review

* fix(channels): include wecom mixed voice text

* fix(channels): tighten wecom outbound media

* fix(channels): harden wecom outbound sends

* fix(channels): address wecom review blockers

* fix(channels): address wecom review followups

* fix(channels): harden wecom inbound handling

* fix(channels): address wecom auth and media review

* fix(channels): tighten wecom inbound cleanup

* fix(channels): harden wecom media safety

* fix(channels): address wecom review typecheck

* fix(channels): harden wecom media review gaps

* fix(channels): address wecom review blockers

* fix(channels): tighten wecom media edge cases

* fix(channels): address wecom review blockers

* fix(channels): address wecom media review blockers

* fix(channels): address wecom review follow-ups

* fix(channels): address wecom review blockers

* fix(channels): close wecom review blockers

* fix(channels): close wecom preflight dedup race

* fix(channels): close wecom review gaps

* fix(channels): harden wecom kick reconnect

* fix(channels): defer wecom session resolution

* fix(channels): clean wecom session attachments

* fix(channels): harden wecom reconnect and media cleanup

* fix(channels): address wecom review diagnostics

* fix(channels): improve wecom diagnostics

* fix(channels): reset wecom kick retries

* fix(channels): improve wecom diagnostics

* fix(channels): preserve sync cancel preflight

* fix(channels): close wecom connection and ssrf gaps

* fix(channels): clean coalesced wecom attachments

* fix(channels): bound wecom sdk connect wait

* fix(channels): scope wecom untracked attachment cleanup

* fix(channels): block wecom nat64 local-use ssrf

* fix(channels): harden wecom media handling

* fix(channels): harden wecom group gates

* fix(channels): bound wecom kick reconnect cycles

* fix(channels): drain loop collect prompts directly

* fix(channels): align wecom buffer hooks

* fix(channels): harden wecom delivery failures

* fix(channels): recover from wecom attachment write failures

* fix(channels): surface wecom media send failures

* fix(channels): harden wecom replay and reconnect

* fix(channels): clarify wecom partial delivery cleanup

* fix(channels): close wecom rejected downloads

* fix(channels): retain wecom dedup after processing starts

* fix(channels): harden wecom reconnect and media errors

* fix(channels): add wecom media error context

* fix(channels): improve wecom dns diagnostics

* fix(channels): keep wecom kick retry alive

* fix(channels): allow wecom quoted bot replies

* fix(channels): preserve wecom code fences across chunks

* fix(channels): harden wecom reconnect lifecycle

* fix(channels): report wecom media dir setup failures

* fix(channels): harden wecom reconnect recovery

* fix(channels): align wecom review fixes

* fix(channels): harden wecom marker parsing

* fix(channels): keep wecom reconnect timers alive

* fix(channels): handle wecom tilde fences

* fix(channels): preserve wecom fence state

* fix(channels): clean up wecom attachment races

* fix(channels): bind wecom media reads to file handles

* fix(channels): prevent wecom symlink media opens

* fix(channels): address wecom review blockers

* fix(wecom): remove media URL from error messages to prevent credential leakage

The guardedHttpsDownload error messages included rawUrl (truncated to 120
chars), which leaks private WeCom media download URLs into stderr and log
aggregation systems. Remove the URL from redirect and HTTP error messages.

* fix(wecom): address review feedback — tests, security, correctness

- Remove stale URL assertions from media download error tests (the error
  messages no longer include raw URLs after the credential-leak fix)
- Redact sensitive fields (secret, aeskey, token, password, authorization)
  in formatSdkError's JSON.stringify fallback to prevent credential
  leakage in logs
- Add indented code block detection to findCodeRanges so [IMAGE: path]
  inside 4-space/tab-indented code is not stripped as a media marker
- Add disconnectGeneration guard before mkdirSync in downloadAttachments
  to prevent orphaned temp directories when disconnect() races with
  in-flight attachment downloads

* fix(wecom): wrap client.disconnect() in catch block to preserve connection error

In the connect() catch block, client.disconnect() could throw (e.g. if
the WebSocket was already destroyed), masking the original connection
error. Wrap in try/catch so cleanup failures never shadow the root cause.

* fix(channels): address wecom reconnect review blockers

* fix(channels): harden wecom reconnect review fixes

* fix(channels): harden wecom review blockers

* fix(channels): address wecom review blockers

* fix(channels): preserve unsupported wecom media markers

* fix(channels): address wecom reliability suggestions

* fix(channels): allow wecom retry after early drops

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-07 15:24:19 +00:00
qwen-code-ci-bot
4e3fd29781
chore(release): v0.19.6 (#6280)
* chore(release): v0.19.6

* docs(changelog): sync for v0.19.6

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-03 16:37:54 +00:00
qwen-code-ci-bot
2126474c28
chore(release): v0.19.5 (#6194)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
* chore(release): v0.19.5

* docs(changelog): sync for v0.19.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-02 13:38:46 +00:00
qwen-code-ci-bot
f3ea17bf43
chore(release): v0.19.4 (#6132)
* chore(release): v0.19.4

* docs(changelog): sync for v0.19.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-01 13:13:47 +00:00
Gaurav
fc184f20e5
fix(deps): clear critical runtime audit findings (#6065)
* fix(deps): clear critical runtime audit findings

* fix(core): allow intentional worktree hooks path setup
2026-07-01 05:28:32 +00:00
jinye
cf6323bfb5
feat(cli): Add daemon-managed channel worker for serve --channel (#6031)
* feat(cli): add daemon-managed channel worker

* codex: address PR review feedback (#5978)

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

* codex: address PR review feedback (#6031)

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

* codex: address PR review feedback (#6031)

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

* codex: address PR review feedback (#6031)

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

* fix(cli): harden serve channel worker lifecycle

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

* fix(cli): cover channel worker edge cases

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

* fix(cli): address channel worker review followups

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

* fix(cli): clear channel pidfile after worker exit

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

* fix(cli): address serve channel review feedback

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

* fix(cli): harden serve channel worker review issues

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

* fix(cli): preserve channel worker exit errors

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

* fix(cli): harden daemon channel worker startup

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

* fix(cli): address channel worker review cleanup

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

* fix(cli): track channel worker exit explicitly

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

* codex: address PR review feedback (#6031)

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

* codex: address daemon worker startup review (#6031)

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

* codex: address PR review feedback (#6031)

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

* codex: address daemon worker disconnect review (#6031)

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

* fix(cli): address serve channel review feedback

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

* fix(cli): address channel worker review feedback

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-01 01:40:54 +00:00
qwen-code-ci-bot
8b65a555a0
chore(release): v0.19.3 (#5952)
* chore(release): v0.19.3

* docs(changelog): sync for v0.19.3

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-28 06:59:17 +00:00
Heyang Wang
1344f34147
feat(mcp): reconcile MCP servers live on settings change (#5561)
* feat(mcp): reconcile MCP servers live on settings change

Hot-reload MCP servers when settings.json changes (issue #3696 sub-task 3):
editing mcpServers / mcp.allowed / mcp.excluded now connects, disconnects, or
restarts only the affected servers in place, without restarting the session or
losing conversation context.

- Part A: Config runtime setters + reinitializeMcpServers incremental
  reconcile; align the shared-pool path with the #4615 pending-approval gate
- Part B: SettingsWatcher subscriber (hotReload.ts), gated on a mcpServers +
  gating-list diff; flip the three MCP schema keys to hot-reloadable
- Part D: re-fire the approval modal for a gated server left pending by an edit
- Part E: /mcp shows why a gated server was skipped (pending / rejected)
- Record connection fingerprints on the bulk and lazy-connect paths so an edit
  to a server first connected via those paths is not silently dropped
- Design doc (en/zh) incl. the admission-stance boundary clarification

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

# Conflicts:
#	packages/cli/src/config/settingsSchema.test.ts

# Conflicts:
#	packages/cli/src/ui/components/mcp/steps/ServerDetailStep.tsx

# Conflicts:
#	packages/cli/src/gemini.tsx

* feat(mcp): reconcile MCP servers live on settings change

Hot-reload MCP servers when settings.json changes (issue #3696 sub-task 3):
editing mcpServers / mcp.allowed / mcp.excluded now connects, disconnects, or
restarts only the affected servers in place, without restarting the session or
losing conversation context.

- Part A: Config runtime setters + reinitializeMcpServers incremental
  reconcile; align the shared-pool path with the #4615 pending-approval gate
- Part B: SettingsWatcher subscriber (hotReload.ts), gated on a mcpServers +
  gating-list diff; flip the three MCP schema keys to hot-reloadable
- Part D: re-fire the approval modal for a gated server left pending by an edit
- Part E: /mcp shows why a gated server was skipped (pending / rejected)
- Record connection fingerprints on the bulk and lazy-connect paths so an edit
  to a server first connected via those paths is not silently dropped
- Design doc (en/zh) incl. the admission-stance boundary clarification

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

* fix(mcp): harden hot-reload teardown and reconcile (review follow-ups)

Address reviewer findings on the MCP hot-reload changes:

- Extract purgeServerRegistries() and use it at every teardown path, fixing
  the discovery-timeout handler which leaked prompts/resources (only tools
  were purged) for a server that stalled tools/list past the timeout.
- Surface reconcile failures via AppEvent.LogError so a failed settings edit
  is visible to the user, not just under --debug.
- Make a single-session config edit to a discovery filter (trust /
  includeTools / excludeTools) reconnect the server so discover() re-applies
  it: connectionIdOf stays transport-only; add singleSessionConnectedKeyOf and
  rename connectionFingerprints -> connectedConfigKeys.
- Make a coalesced reinitializeMcpServers await the in-flight pass + its drain
  (store mcpReconcilePromise) so the caller no longer emits approval events /
  logs "complete" before its change is applied; coalesced callers share the
  failure.
- Assert removeResourcesByServer in the fingerprint-change tests.

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

* feat(mcp): bound hot-reload MCP admission and explain why servers are unavailable (#3696) (#5561)

- K: treat the startup --allowed-mcp-server-names flag as an immutable upper
  bound — a runtime settings edit may narrow MCP admission within it but never
  widen beyond it; with no flag, settings fully drive admission.
- H: preserve an explicit `mcp.allowed: []` as deny-all (don't collapse to
  undefined / allow-all), matching boot semantics, and make mcpGatingEqual
  distinguish absent (allow-all) from [] (deny-all) so the change reconciles.
- B: classify why an MCP server is unavailable (removed / not_allowed /
  excluded / pending_approval) and route the tool-not-found message to the
  right recovery action; track removals against the gating-independent merged
  map (dropping the prev-effective snapshot param).

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

* docs(mcp): document hot-reload admission bound, deny-all, and unavailable reasons (Part F)

Reflect the K/H/B changes in the sub-task 3 design doc: add Part F (CLI
--allowed-mcp-server-names as an immutable upper bound, mcp.allowed: [] as
deny-all, and getMcpServerUnavailableReason routing the tool-not-found message),
and fix the now-superseded "settings can widen beyond the startup allowlist"
admission-stance note and verification item 11.

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

* test(serve): pre-approve gated MCP servers in daemon baseline harness

The pool/daemon discovery path now honors #4615 pending-approval gating, so the workspace-scoped MCP servers the amplification suite declares in .qwen/settings.json are skipped as pending and never spawn (the suite timed out waiting for grandchildren). Add approveWorkspaceMcpServers() to the harness (keyed by the realpath workspace to match the daemon's canonicalized --workspace) and pre-approve the fixtures before boot, mirroring simple-mcp-server.test.ts.

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 14:50:37 +00:00
qwen-code-ci-bot
5bb79bc67c
chore(release): v0.19.2 (#5830)
* chore(release): v0.19.2

* docs(changelog): sync for v0.19.2

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-24 15:00:58 +00:00
qwen-code-ci-bot
8eb5770812
chore(release): v0.19.1 [skip ci]
* chore(release): v0.19.1

* docs(changelog): sync for v0.19.1

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 17:26:10 +08:00
qwen-code-ci-bot
57156522bd
chore(release): v0.19.0 [skip ci]
* chore(release): v0.19.0

* docs(changelog): sync for v0.19.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-23 11:38:15 +08:00
qqqys
b9c5e3566b
feat(voice): voice dictation with native capture, streaming, and biasing (#5502)
* feat(voice): voice dictation with native capture, streaming, and biasing

Add voice dictation for the prompt input:
- /voice [hold|tap|off|status] command + general.voice.{enabled,mode,language,protocol} settings; push-to-talk via Space, /model --voice to pick the model
- Native microphone capture (@qwen-code/audio-capture, miniaudio N-API) with arecord/SoX fallback, silence auto-stop, cold-start warm-up, and macOS permission query
- Batch transcription via DashScope Qwen-ASR (OpenAI-compatible chat/completions + input_audio) with language + keyterm biasing and an echo guard
- Live streaming over the DashScope realtime WebSocket (fun-asr-realtime / paraformer-realtime-v2) with interim text and an input-level waveform, behind voice.protocol=dashscope-realtime
- Rich VoiceIndicator UI (state, level meter, live partial transcript)
- Cross-platform prebuilds via prebuildify + node-gyp-build and a CI matrix

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

* feat(cli): route voice ASR by model

* feat(cli): polish voice realtime parity

* ci: fix voice workflow checks

* fix(cli): address voice review blockers

* fix(cli): harden voice transcription failures

* fix(cli): address voice PR review blockers

* fix(cli): harden voice review follow-ups

* fix(voice): handle realtime review blockers

* fix(cli): add voice command i18n keys

* fix(cli): address voice review blockers

* fix(cli): address voice review follow-ups

* fix(voice): harden review edge cases

* fix(voice): address release and realtime review blockers

* fix(voice): address realtime suggestion followups

* fix(voice): handle realtime review followups

* fix(voice): address realtime review blockers

* fix(voice): address review feedback

* fix(voice): address recorder review feedback

* docs(voice): document ssrf guard boundary

* fix(voice): address review feedback

* fix(voice): preserve warm recorder session safety

* fix(voice): address stream review suggestions

* fix(voice): address multi-round review findings

Realtime/streaming:
- Salvage an already-committed transcript when the WebSocket closes right
  after finish() instead of rejecting the whole dictation
  (qwenAsrRealtimeSession, voiceStreamSession) + regression tests.

useVoiceInput state machine:
- Single-shot finalize guard so a tap-stop racing the silence auto-stop can't
  double-stop the recorder and surface a spurious failure.
- Reset mountedRef on (re)mount so StrictMode (DEBUG) can't freeze the voice UI.
- Widen the hold-mode first-press release window above common key-repeat delays.

Model selection:
- Reject ids with no ASR transport at /model --voice and in the model dialog via
  a new isSelectableVoiceModel; move resolveVoiceTransport into voiceModel so the
  record-time config resolver stays transport-agnostic (+ tests).

macOS mic permission:
- Surface the not-determined state in voice warmup so the first dictation isn't
  silently lost behind the TCC dialog.

Native packaging:
- Make the audio-capture native install non-fatal (falls back to SoX/arecord) so
  a voice-only build failure can't break npm ci.
- Download audio-capture prebuilds before building standalone release archives.

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

* fix(voice): address review blockers

* fix(voice): update batch recording audio level

* fix(voice): tap-mode transcript loss, stream leaks, and dead keyterm cleanup

- Tap-mode dictation submitted a stale empty buffer.text, wiping the just-
  inserted transcript and sending nothing: thread the resulting prompt text
  through onSubmit(text) instead of reading buffer.text back synchronously
  after the async insert (useVoiceInput, InputPrompt).
- Streaming finalize leaked the WebSocket session when recorder.drain() threw:
  abort the session before propagating the error.
- voiceStreamSession: reject the connect promise when 'task-finished' arrives
  before 'task-started' instead of hanging forever in 'transcribing'.
- Remove dead keyterm enrichment (project/branch/recent-file paths were
  unreachable after the privacy fix) and the now-inert CJK echo guard, plus the
  tests that asserted that removed behavior; fix the misleading "OpenAI prompt
  field" comment.
- Add the missing 'Voice Model' and macOS mic-permission i18n keys to
  en/zh/zh-TW (they were falling back to English; check-i18n doesn't flag keys
  absent from en).

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

* fix(voice): reject partial stream transcripts

* fix(cli): let dialogs consume voice keys first

* fix(voice): reject incomplete qwen realtime transcripts

* fix(voice): handle stream review blockers

* fix(voice): salvage qwen realtime transcript on close

* fix(voice): sanitize streamed transcript text

* test(audio): run audio capture tests in CI

* fix(voice): address review blockers

* fix(voice): report stream close while recording

* fix(voice): reduce keyterm echo false positives

* fix(voice): handle realtime close diagnostics

* fix(voice): address realtime review blockers

* fix(lint): allow legacy voice filenames

* chore(cli): rename voice files to kebab-case

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-06-22 14:33:36 +08:00
qwen-code-ci-bot
8f8ed0d7c1
chore(release): v0.18.5 [skip ci]
* chore(release): v0.18.5

* docs(changelog): sync for v0.18.5

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 21:13:19 +08:00
qwen-code-ci-bot
c5fb75b5c2
chore(release): v0.18.4 [skip ci]
* chore(release): v0.18.4

* docs(changelog): sync for v0.18.4

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-06-21 03:36:38 +00:00
Heyang Wang
a8eb824fb7
feat(config): add settings file change detection via chokidar watcher (#3696) (#4933)
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-06-19 15:00:44 +08:00