* feat(triage): lead the verify comment with a qualitative verdict, fold the Chinese
Maintainer feedback on the first 14-run day of the lane, pointing at the
report on PR #7836: give a plain pass/no-pass, and make the layout
English-by-default with the Chinese folded (the repo's PR-body
convention), instead of doubling every head line.
Three changes:
- every headline leads with a qualitative call — ✅ passed / ❌ not
passed / ⚠️ inconclusive / ⚠️ incomplete — ahead of the lane taxonomy.
The mapping is honest about what each outcome can claim: only agent
verdicts judge the code; a crashed, timed-out, or verdict-less run
renders as incomplete/inconclusive, never as a pass or a fail, and a
test pins that no process-outcome arm carries ✅ or ❌.
- the Chinese version folds into one <details> whose SUMMARY carries the
qualitative verdict — collapsed, a Chinese reader still sees
通过/不通过 in one line. English scope and assertion count stay
unfolded; both language assertion lines are built from the same
validated triple, so inconsistent JSON still suppresses both.
- the #7836 root cause gets a Hard rule in the verify-pr skill. That
report said "merge-ready — the 7 failures are all expected A/B
base-cell failures proving the tests load-bearing" while
assertions.json said fail:7, so the publisher's trust rule (merge-ready
requires fail==0) correctly refused it and the headline degraded to
"no usable structured verdict". Both sides told the truth about
different questions. The rule fixes the semantics: an A/B control cell
is an assertion that the base arm FAILS — when it does, that assertion
PASSED. fail counts only unexpected outcomes, which is what makes a
qualitative headline trustworthy at a glance.
Mutation-verified 6/6: dropping the qualitative prefix, collapsing all
Chinese quals onto one string, unfolding the Chinese scope, giving
timeout a ✅, dropping the folded Chinese assertion line, and deleting
the new Hard rule each turn at least one test red. The fifth mutation
initially reported "landed: False" — the regex never matched and the
file was untouched, so the green result proved nothing; re-applied as a
line filter with a removed-block count, it kills two tests.
One new shellcheck SC2016 info finding (backticks inside a single-quoted
Chinese printf), same shape as the 11 pre-existing ones; info-level,
does not gate.
* test(triage): pin the dropped verdict arms and prepare-failure glyphs (#7974)
Restore rendering coverage for the reachable infra-error and unknown catch-all case arms in the qualitative-headline bijection, and assert the prepare-failure path's qualitative glyphs, Chinese fold summaries, and the Chinese retry clause so a swapped emoji or dropped PREPARE_ATTEMPTS_ZH interpolation can no longer ship undetected.
* fix(triage): migrate all weak headlines, explain merge-ready mismatch (#7974)
* fix(triage): fold Chinese mismatch explanation, differentiate distrust warnings (#7974)
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code CI Bot <qwen-code-ci-bot@users.noreply.github.com>
* fix(cli): map Kitty Super (Command) modifier to meta
Kitty-protocol terminals forward Cmd+C as a CSI-u sequence whose modifier
parameter carries the Super bit (8), which the parser silently dropped. The
key then parsed as a bare printable "c" (meta: false) and leaked into the
input box even though the terminal performed the copy itself. Fold the Super
bit into the emitted meta flag at every Kitty modifier decode site so
Super-modified keys are no longer inserted as text.
Fixes#7990
* test(cli): cover Super-bit fold on reverse-tab and functional-keys paths (#7996)
* test(cli): make functional-key Super-bit case load-bearing
Use Home (ESC [ 1 ; 9 H) instead of Up arrow (ESC [ 1 ; 9 A): readline
claims modified arrows before the Kitty arrowPrefix decoder, so the arrow
case passed even without the Super-bit fold. Home exercises the decoder
and fails on unfixed code.
---------
Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* ci: add Windows runner smoke test
* ci: use built-in Windows PowerShell
* ci: secure Windows runner smoke trigger
* ci: run full test suite in Windows validation
* ci: keep Windows validation manual
The DingTalk interactive cards change (#6930) added concurrent
session-cancellation coalescing to DaemonSessionClient, growing the
minified browser daemon bundle to 180295 bytes — 71 bytes over the
176KB budget, breaking npm run build on main. Bump the budget to
177KB following the established pattern.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(release): skip notes-start-tag when previous release diverges from target (#7969)
* style(release): fix prettier formatting in release workflow (#7969)
* fix(release): warn when notes-start-tag is skipped for a divergent tag (#7969)
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* fix(serve): allow bounded reads of large text files
* fix(serve): bound large-text reads by scan cost, not by which knob was set
Follow-up to the bounded large-text read path. Three changes:
Gate on any explicit window argument, not on `limit`. Gating on `limit`
had the cost model backwards in both directions: `{ line: 900_000_000,
limit: 20 }` was admitted despite walking the whole file, while
`{ maxBytes: 4096 }` — satisfiable from the first 4 KiB — was refused. A
read with no window argument at all still fails, since a caller that
believes it holds the whole file may write it back truncated.
Add MAX_TEXT_SCAN_BYTES (8 MiB). MAX_READ_BYTES caps what a read
returns; nothing capped what it cost. Line offsets are resolved by
scanning from byte 0, so a query param could turn into an
uninterruptible multi-second scan of an arbitrarily large file — and on
Windows hold a read handle for that span, blocking renames and deletes.
Past the budget the read is refused with `file_too_large` pointing at
readBytes, which reaches any offset in O(1).
Tolerate appends on streamed windows. Requiring whole-file size/mtime
stability after reading a prefix rejected reads whose returned bytes
were still valid, and the case it rejected — tailing a live log — is the
one this path exists for. Streamed windows now assert inode identity
plus "did not shrink"; truncation and replacement are still rejected.
Also: non-UTF-8 large text now returns `binary_file` rather than
`file_too_large`, so a client retrying on 413 with a smaller window
can't loop forever; and `readFileWithLineAndLimit` throws instead of
silently ignoring a caller-supplied `fileHandle` on the by-path
fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(serve): harden large text range snapshots
Treat caller-owned file handles as bounded streaming reads, cap them to the captured file size, and reuse the chunk buffer.
Restore strict Serve snapshot stability and align returned-slice metadata with the full-snapshot path.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): make large text ranges snapshot-safe
* fix(serve): harden large-text ctime tests and document buffer reuse (#7947)
Address review feedback on the large-text range read PR:
- Pause before restoring mtime in the two ctime-dependent mutation tests so the change-time advances past the pre-read snapshot even on coarse-resolution filesystems, removing a latent flake in the same-size-overwrite precondition. The assertions are unchanged.
- Document at the readFileHandleChunks yield site that the 512 KiB buffer is reused across iterations, so yielded views must be decoded or copied before advancing the generator.
* docs(serve): soften same-size rewrite guarantee to coarse-clock best-effort (#7947)
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
On Windows (pasteWorkaround path), shouldFlushRawDataAsPaste
misclassifies raw stdin data containing both a carriage return (0x0d)
and an SGR mouse sequence as pasted content. The synthetic paste
wrapper sets isPaste=true in handleKeypress, which discards SGR mouse
data via resetSgrMouse(), breaking wheel scrolling in VP mode.
This commonly triggers when Enter (\r) and a subsequent mouse wheel
event arrive in the same stdin chunk — a frequent occurrence on
Windows Terminal. macOS is unaffected because handleStdinData does
not use this heuristic.
Skip the paste heuristic when the buffer contains ANSI escape bytes
(0x1b) so SGR mouse sequences always reach readline for proper
parsing and dispatch to mouse subscribers.
Closes#7964
Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* fix(cli): correct hardware cursor off-by-one in fullscreen mode
In fullscreen mode Ink omits the trailing newline, so after writing
output the terminal cursor sits ON the last line rather than one past
it. buildCursorSuffix assumed the latter and emitted one extra
cursorUp, placing the hardware cursor (used for IME positioning) one
row above the software block cursor — visible as an extra white
rectangular segment protruding above the input line.
Pass a hasTrailingNewline flag through buildCursorSuffix,
buildReturnToBottom, buildCursorOnlySequence, and
buildReturnToBottomPrefix in the Ink patch so both the standard and
incremental log-update renderers compute the correct cursor movement.
Closes#7980
* fix(cli): revert incorrect hasTrailingNewline in buildReturnToBottom
Self-review caught that buildReturnToBottom does NOT need the
hasTrailingNewline adjustment: its previousLineCount argument comes
from str.split('\n').length, which already includes the trailing
empty element when the output ends with '\n'. The original formula
(previousLineCount - 1 - y) is the exact inverse of the corrected
buildCursorSuffix in both fullscreen and non-fullscreen modes.
Only buildCursorSuffix needs the flag, because its visibleLineCount
argument excludes the trailing empty element.
* fix(cli): sync cursor-helpers.d.ts and add fullscreen cursor-only test (#7998)
---------
Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
* fix(web-shell): preserve session URL context
* fix(web-shell): keep daemon token out of the session URL (#7926)
Restore the daemon token stripping that was dropped alongside the
base-path fix: removeDaemonTokenFromUrl() on startup and the ?token=
delete in replaceStandaloneSessionUrl. The ?token= query path is still
supported for backward compatibility, so without stripping it leaks into
the address bar, history, access logs, and Referer headers.
Also extract the session pathname building into buildSessionPathname()
with unit tests covering root/sub-path deployments, the no-session case,
trailing slashes, and id encoding.
* fix(web-shell): anchor session URL parser to agree with writer (#7926)
Extract parseSessionId() next to buildSessionPathname() and anchor it to the last /session/<id> segment so the parser agrees with the greedy writer. Previously a base path ending in a session segment produced /app/session/session/<id>, which the first-match parser read back as the literal id "session". Add round-trip and trailing-slash coverage.
* test(web-shell): cover parseSessionId malformed-encoding catch branch (#7926)
* fix(web-shell): preserve base path in split-view URL (#7926)
---------
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
* fix(core): mark folders the item budget never expanded
A folder is pushed into its parent's subFolders and queued for expansion.
When it is later dequeued and the maxItems budget is already spent, the
BFS skips it with a bare `continue`, leaving hasMoreFiles and
hasMoreSubfolders unset. It then formats as a bare leaf -- byte for byte
how a genuinely empty directory formats.
So the same tree was described differently depending only on the budget.
The existing suite shows it: `level1/level2/level3/file.txt` renders
level3 with its file at maxItems 10, and as an empty directory at
maxItems 3. The subfolder test is worse -- folder-0 through folder-3 each
contain child.txt and all four render as empty.
The parent's hasMoreSubfolders flag does not cover this. It means "I could
not add every subfolder", and these folders were added; it is only the
expansion that never happened.
Set hasMoreSubfolders on the skipped node so it renders with the
truncation indicator. Only that one flag is set: formatStructure emits an
indicator per flag, and this one alone produces the single trailing `...`.
Three existing expectations encoded the old rendering and are updated.
* docs(core): state both meanings of hasMoreSubfolders on the interface
This PR widened the field: it already meant "subfolders were truncated" and
now also means "the item budget ran out before this folder was read." The
interface comment only described the first, so a second consumer reading just
the type could gate rendering on `subFolders.length > 0` and silently drop the
indicator for never-expanded folders, reintroducing the same
looks-like-an-empty-directory bug in a new path.
* fix(core): improve ripgrep runtime reliability
Make ripgrep failures distinguishable from true no-match results so the
model avoids unsafe conclusions from partial or failed searches. Add a
narrow recovery path for confirmed worker-thread EAGAIN failures.
- Retry confirmed thread EAGAIN once with a single worker thread
- Treat exit code 1 as no-match only when both streams are empty
- Mark partial runtime results as incomplete, separate from display limits
- Add privacy-safe telemetry and focused coverage for recovery behavior
# Conflicts:
# packages/core/src/utils/ripgrepUtils.test.ts
* fix(core): restore ripgrep runtime recovery tests
Restore the runRipgrep coverage that was lost during the rebase and keep
EAGAIN detection aligned with the runtime reliability design.
- Re-add coverage for strict no-match handling and incomplete output
- Verify single-thread retry behavior for confirmed EAGAIN failures
- Keep spawn, cancellation, timeout, and max-buffer paths covered
- Treat os error 11 as the short EAGAIN marker documented by the plan
* docs(core): document ripgrep recovery boundaries
Clarify the non-obvious runtime reliability edges around ripgrep recovery so
future changes preserve the intended narrow behavior.
- Document why only confirmed worker EAGAIN failures are retryable
- Explain incomplete-output handling for interrupted ripgrep output
- Note the privacy boundary for runtime recovery telemetry
* fix(core): correct exit-1 no-match gate for --json summary output (#7888)
* test(core): remove duplicate mockReset and add telemetry coverage (#7888)
* fix(core): address review feedback on ripgrep recovery semantics (#7888)
- Fix stale `truncated` property in test mock to match RipgrepRunResult
- Narrow `incomplete` flag to genuinely interrupted executions only
- Add test for exit code 1 with both stdout and stderr
- Add test for EAGAIN retry producing partial stdout
- Alias RipgrepRuntimeRecoveryFailureKind to RipgrepFailureKind
* docs(core): align exit-1 no-match spec with stderr-only gate (#7888)
* fix(core): mark exit-failed ripgrep searches with stdout as incomplete (#7888)
---------
Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
* feat(external-context): add submitted-prompt auto recall
Add an opt-in Hook-only profile that derives bounded retrieval queries from submitted prompt provenance while preserving the existing on-demand MCP contract.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(external-context): harden auto recall query sanitization (#7877)
Address review feedback on the submitted-prompt auto recall Hook:
- Bound the sanitizer input before the redaction regexes run so a
worst-case prompt cannot drive the assignment regex into quadratic
backtracking that blocks the event loop past the wall-clock budget.
- Test the whole assignment for secret names so a leading label such as
"Deploy failed:" can no longer claim the match and leak an api_key=.
- Skip the interactive E2E under container sandboxes (docker/podman),
matching the cron-interactive precedent.
- Restore real undici coverage for a malformed proxy environment value.
- Make the wall-clock-budget test exercise the internal timer rather than
the provider timeout, and give the backtracking regression test a shape
that actually backtracks.
- Clarify that the v2 top-level timeoutMs applies only to the on-demand
MCP path, and note session-lifetime context accumulation in the design
doc.
* fix(external-context): complete secret redaction, guard MCP config version (#7877)
Anchor the secret keyword to the name that owns the separator so a leading
prose label can no longer claim the match. This redacts spaced separators
(api_key = sk-...) and inline JSON ({"api_key": "..."}), and stops
over-redacting ordinary prose such as "readme: token refresh flow".
Also reject non-version-1 configs in runMcp with a clear startup error so an
auto-recall (v2) config cannot silently expose a second retrieval surface.
---------
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Post-merge measurement of #7917, one day in: 9 eligible PRs, two
considered-and-declined mentions (both correct calls), zero positive
recommendations. The one clear behavioural candidate — #7947, bounded
reads of large text files — wrote "Not verified: Windows and Linux
manual runs (author tested on macOS only)" in its own Stage 2 comment
and never named a lane.
That is the failure shape worth fixing: the judgement-based rule ("when
neither static review nor 2b substantiates it") failed exactly where
the comment had already written the gap down in so many words. The
model judged that pending CI would cover it; a green suite proves the
tests pass, not that the untested behaviour holds.
So the trigger is now textual, not judgemental: before posting, grep
your own draft. A sentence of the shape "not verified", "author tested
on one platform only", or "author's claim, not independently re-run"
IS the trigger — the 2b-bis line is that same sentence with the remedy
attached, and omitting it means telling the maintainer what is missing
while withholding the one command that would supply it. Pending CI
does not lift the trigger. The two legitimate skip cases (nothing
behavioural to settle; author lacks write) are unchanged, and the rule
is ordered before them so they read as outs from the requirement, not
the requirement as an out from them.
The trigger phrases are verbatim from real comments: "not verified"
and "author tested on macOS only" from #7947, "author's claim, not
independently re-run" from #7951.
Mutation-verified 3/3: dropping the trigger paragraph, moving it after
the skip cases, and dropping the pending-CI sentence each turn the test
red. n=1 is thin evidence for a behavioural rule change — but this rule
is text-matching, not probability-weighing, so it cannot overfit to the
sample that motivated it.
Co-authored-by: wenshao <wenshao@example.com>
On reused GitHub-hosted runners, a previous job (e.g. verify/tmux on
the same runner pool) can leave .qwen/ files with restrictive
permissions (chmod -R a-w). The next job's checkout step then fails
with "error: unable to unlink old '.qwen/...': Permission denied"
because git cannot delete the read-only files during workspace
cleanup.
Add a pre-checkout step that chmods .qwen/ writable and removes it
before the checkout action runs. This is a no-op on fresh runners
where .qwen/ doesn't exist yet.
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: 易良 <1204183885@qq.com>
* feat(core): integrate Goal turn engine
* test(core): preserve Goal tool isolation in agent overrides
* fix(core): pause Goal at Stop hook cap
* fix(core): keep Goal recovery from blocking sessions
* fix(core): degrade failed Goal migration writes
* fix(core): reset loop detector in Goal stop hook continuation (#7895)
The goal-runtime stop hook continuation path was missing
loopDetector.reset(prompt_id) before recursing, causing tool calls
to accumulate across iterations and trip TURN_TOOL_CALL_CAP after
a handful of healthy iterations. The non-goal stop hook path already
had this reset.
Also simplifies the redundant conditional in Turn.run() — the two
near-identical sendMessageStream calls are collapsed into one since
sendMessageStream already handles an undefined goalContext internally.
* fix(core): align turn.test.ts assertion with unified sendMessageStream call (#7895)
* fix(core): preserve error cause in GoalPersistenceUnavailableError (#7895)
---------
Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
The PR review job and the containerised triage jobs all built their gh/git
proxy wrappers in one fixed directory under RUNNER_TEMP. On the shared
self-hosted runner that path outlives a job, and the triage containers write
it as root through the RUNNER_TEMP bind mount. Once that happened, the review
job — running as the unprivileged runner user — could neither overwrite the
wrapper nor remove the root-owned directory holding it, so every review
scheduled on that runner died with EACCES while writing the wrapper, before
the review itself had started.
Each run now creates a private wrapper directory and removes it on exit, and
the container jobs keep theirs on the container's own disk so they stop
leaving root-owned state on the host.
* fix(triage): make the build-process guard diagnosable and zombie-aware
The first real /verify run to reach the agent step (job 30267953352) was
stopped by the guard I added to stop detached lifecycle children from
outliving their step:
::error::Processes owned by the build user survived; refusing to start
the agent.
That is all it printed. No pid, no state, no command line — so a genuine
threat and a harmless leftover were indistinguishable, including to the
person who wrote it. The control had no observability of its own.
Two changes, both in the verify and tmux lanes:
- name the survivors. The error now lists each one as pid, state and
command line, so the next occurrence can actually be diagnosed.
- disregard zombies. A zombie has already exited and released everything
except its exit status: it cannot re-plant an artifact or touch the
agent's inputs, and it cannot be killed either — so counting one means
the check can never clear, no matter how many times SIGKILL is sent.
The container runs no init to reap orphans, so they are expected here.
The platform difference is the reason the old form could hang: Linux
pgrep reports defunct processes ("Defunct processes are reported." —
pgrep(1), procps-ng), while macOS pgrep does not list them at all
(verified directly: ps shows our zombie, pgrep does not). So this failure
mode was unreachable in local replay and only appears on the runner.
That same difference shapes the tests. The behavioural arm constructs a
real zombie, confirms it survives SIGKILL, and shows the state filter
drops it — but it cannot discriminate the old implementation from the new
one on macOS, because pgrep never saw the zombie there in the first place.
So the portable guarantee is asserted structurally: the filter must read
process state and exclude Z, and must not be a bare pgrep. Mutation-
verified 3/3 — removing the state filter, dropping the survivor names, or
reverting the message each turn one test red.
Also gives both proxy-watchdog tests an explicit timeout. They stream 20
chunks at 200 ms — 4 s before the stall arm even starts — so they cannot
fit vitest's 5 s default and were timing out on main.
* fix(triage): tolerate zero-process exit in build guard (#7858)
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* feat(triage): make the verify report readable in Chinese
The scope disclaimer under the verify headline has been bilingual since
the lane shipped. The verdict above it was not, and neither was the
assertion count — so a Chinese reader of the unfolded comment got the
caveat ("advisory evidence, not a review") and not the conclusion.
Three changes, all on the parts a reader reaches first:
- every headline carries a Chinese twin, across all eight arms —
merge-ready / findings / blocked / inconclusive from the agent, and
completed / fail / timeout / infra-error from the process outcome;
- the assertion count renders in both languages, off the same validated
object, so an inconsistent assertions.json still suppresses both and
the comment cannot grow a number no gate checked;
- in the report itself, 中文摘要 moves from last item to second, right
after the verdict. The whole report is already inside a <details> on
the PR, so leaving the Chinese summary at the bottom meant expanding
that fold and scrolling the entire English report — about 90 lines on
a real one — to reach the one section written for that reader. It
stays collapsed, so it costs everyone else exactly one line.
The headline test pins the pairing as a bijection, not as containment:
asserting only that each arm renders some Chinese leaves a single
hardcoded string — or one that echoes the English — passing every arm.
Mutation-verified 4/4: dropping the Chinese headline, collapsing all
arms onto one Chinese string, dropping the Chinese assertion sentence,
and moving 中文摘要 back to the end each turn one test red.
* fix(ci): correct cross-reference direction and cover all verdict branches in bilingual headline test (#7918)
* fix(ci): narrow bilingual headline comment to match actual scope (#7918)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
The channel adapters depend on @qwen-code/channel-base via a caret range
(^0.21.0). A prerelease bump such as 0.21.1-preview.0 does not satisfy
that range, so the per-workspace npm version reifies replaced the
workspace link with the stale registry package, and the release build
compiled the adapters against outdated types (missing
PollingChannelBase), failing the publish job.
Pin the adapters' channel-base dependency to the exact new version
during scripts/version.js, refresh the install, and drop the stale
nested copies npm leaves under the adapters.
Measured on 2026-07-28 across 22 open PRs: of the 16 whose AUTHOR had
write access and that already carried a triage comment, exactly 1
mentioned `/verify`. The lane it recommends has produced real evidence
on four PRs (#7829 2565 assertions, #7821 1347, #7830 27, #7881 17), so
the gap is not that the lane is useless — it is that almost nobody is
told it exists.
The instruction was there the whole time. It was a conditional clause
inside a section headed "2c. Real-Scenario Testing — local invocation
ONLY" whose first words are "Never in unattended CI." An agent running
in CI reasonably skips that section, and the assembly order at the end
of Stage 2 only allotted 2c a slot "when one was driven locally" — so
on the CI path there was no place for the recommendation to go even if
it had been read.
Split the CI-path half out into its own section, 2b-bis, sited
immediately after the CI-evidence step it follows from: 2b tells you the
suite is green, and cannot tell you the suite pins the change. It is a
required element of the Stage 2 comment when the central claim is
behavioural, with two explicit skip conditions — nothing behavioural to
settle, or the author lacks write (both lanes execute the author's code,
so recommending them on an external contributor's PR is a guaranteed
denial). It must name the specific unsubstantiated claim, because a bare
"you could run /verify" is noise and noise is why the line got skipped.
Stage 1e carried the same dead pointer, and worse: on the high-risk
paths — the strongest triage-time signal in the skill, 10 of 31 reverted
PRs against 5 of 60 controls, p = 0.006 — it recommended tmux alone and
never named /verify. The PRs most likely to be reverted were the ones
never offered the lane that proves a change is load-bearing. 1e now
points at 2b-bis and names both lanes.
The fix is positional, so the tests are positional: asserting that the
file mentions `/verify` would have passed throughout the entire period
the recommendation was dead. Mutation-verified 4/4 — moving 2b-bis back
below the local-only heading, dropping it from the assembly order,
dropping the author-write carve-out, and reverting 1e to the tmux-only
pointer each turn a test red. Prose assertions are whitespace-normalised
and shown to survive a maximal re-wrap, since prettier reflows this file.
Co-authored-by: wenshao <wenshao@example.com>
* feat(web-shell): add git branch picker, commit dialog, and create PR flow
Add an IntelliJ-style branch picker popover to the web shell git
workspace, accessible from the branch chip in both the composer toolbar
and sidebar. The picker provides search-filtered branch listing (local,
remote, tags, recent), branch checkout, new branch creation, pull, push,
and a commit view integrated into the existing GitDialog.
The commit view reuses the diff panel (expandable file diffs with syntax
highlighting, fullscreen support) and adds a commit message textarea with
Commit / Commit and Push buttons. When a session is available (or one is
auto-created), the commit message and PR title/body are generated via the
model using session side-queries (btwSession), giving the agent full
conversation context for accurate generation.
The Create PR flow provides an inline form with auto-detected base branch
and model-generated title/description, backed by a new daemon route that
shells out to gh pr create.
New daemon routes:
- GET /workspaces/:workspace/git/branches
- POST /workspaces/:workspace/git/checkout
- POST /workspaces/:workspace/git/branch
- POST /workspaces/:workspace/git/push
- POST /workspaces/:workspace/git/pull
- POST /workspaces/:workspace/git/commit
- POST /workspaces/:workspace/github/prs/create
- GET /workspaces/:workspace/github/default-branch
* fix(web-shell): resolve correct workspace session for AI generation
The commit message and PR title/body generation now resolves the
most recent session for the target workspace via listWorkspaceSessions,
rather than using the globally active connection.sessionId which may
belong to a different workspace or session. Falls back to creating a
new session only when no sessions exist for the workspace.
Also improves the PR body editor with an Edit/Preview toggle using
the existing Markdown component, and updates the generation prompt
to follow the project PR template structure from AGENTS.md.
* fix(web-shell): stabilize session resolver prop to prevent infinite re-generation
Pass resolveSessionForWorkspace as a stable useCallback reference
instead of an inline arrow function. The inline function created a
new reference on every App render, causing the GitDialog useEffect
to abort and restart generation in an infinite loop.
* fix(web-shell): group remote branches by remote name and add PR target branch dropdown
Remote branches in the branch picker are now grouped by remote
(origin, upstream, etc.) with sub-headers, making fork workflows
clear. The PR create form's base branch field is now a select
dropdown populated from the workspace's branch list, grouped by
remote with optgroup labels, instead of a free-text input.
* fix(web-shell): use ref for session resolver to prevent effect re-run abort
When resolveSessionForWorkspace creates a new session, it updates
connection.sessionId in the provider, which changes the useCallback
reference, which triggers the useEffect to re-run and abort the
in-flight btwSession generation. Store the callback in a ref so the
effect never depends on its identity.
* fix(web-shell): resolve session per workspace, not from global active session
The commit/PR generation effects used connection.sessionId directly
without checking if it belongs to the target workspace. When opening
the commit dialog from a sidebar workspace different from the active
session's workspace, the wrong session was used for generation,
producing incorrect content. Now always routes through
resolveSessionForWorkspace(workspaceCwd) which checks workspace
membership before reusing the active session.
Also replaces 'PR' with 'Pull Request' / '合并请求' in all UI strings
and adds error logging to the generation catch blocks.
* fix(web-shell): retry btwSession with fresh session when stale session detected
When listWorkspaceSessions returns a session that no longer exists in
the daemon's memory (e.g. after daemon restart), btwSession fails with
'No session with id ...'. The generation effects now catch this error,
force-create a new session via resolveSessionForWorkspace(cwd, true),
and retry the btwSession call once. Both btwWithRetry and
resolveSessionForWorkspace are stored in refs to avoid useEffect
dependency chain aborts.
* fix(web-shell): base PR generation on branch diff, not working tree
PR title/body generation now fetches the commit log between the
resolved base branch and HEAD (git log <base>..HEAD) plus any
uncommitted changes, instead of only the working tree diff. The
base branch is resolved inside the effect's promise chain (not
from state) to avoid stale values and dependency warnings. Also
adds range parameter support to fetchGitLog and workspaceGitLog.
* feat(web-shell): replace PR base branch select with searchable popover
The native <select> for the PR target branch is replaced with a
custom searchable popover (BranchSelect) that shows a search input
and a filtered branch list grouped by remote. The default selection
is the target repository's main branch (resolved via
getDefaultBranch). Supports filtering by typing in the search box.
* fix(web-shell): show full remote ref in branch select (origin/main)
Branch select now stores and displays full remote refs like
origin/main instead of stripped names. getDefaultBranch returns
the full ref (origin/main) instead of stripping the prefix.
When creating the PR, the remote prefix is stripped for the
gh pr create --base flag (origin/main → main).
* fix(web-shell): stop pointer propagation in branch picker list to prevent popover dismiss
Radix Popover's outside-click detection was incorrectly firing when
clicking section headers (Recent/Local/Remote/Tags) inside the popover
content, causing the popover to close immediately. Adding
onPointerDown stopPropagation on the list container prevents pointer
events from reaching Radix's document-level handlers.
* fix(web-shell): use onPointerDownOutside guard for branch picker popover
The previous stopPropagation approach failed because Radix Popover
uses capture-phase document listeners for outside-click detection,
which fire before bubble-phase stopPropagation. The correct fix is
onPointerDownOutside on PopoverContent: when Radix incorrectly fires
the outside handler for a click that is actually inside the content
(can happen with portal containers), we check contentRef.contains()
and preventDefault to keep the popover open.
* fix(web-shell): stop click propagation on branch picker popover content
Root cause: the ChatEditor composer container has onClick that calls
core.focus(), stealing focus from the popover. React synthetic events
bubble through the React tree (not DOM tree), so portaled popover
clicks reach the container handler. Radix then detects focus-outside
and dismisses the popover.
Fix: onClick stopPropagation on PopoverContent, matching the existing
pattern in GitModePopover and ToolbarPopover which already have this
fix with an explanatory comment.
* docs: add PR verification screenshots for branch picker feature
* fix(web-shell): mock useWorkspace in tests for BranchPickerPopover
BranchPickerPopover calls useWorkspace() which requires
DaemonWorkspaceProvider context. The existing WorkspaceSection and
ChatEditor tests didn't provide this context, causing 5 test failures.
Added vi.mock with importActual to preserve other exports while
providing a mock useWorkspace. Also updated the git chip click test
to reflect that clicking now opens the branch picker popover instead
of directly calling onOpenGitDiff.
* fix: harden git write paths against argument injection
Address review feedback on the web-shell git surface:
- Reject option/pathspec injection in git checkout ref and branch start
point (isValidCheckoutRef), and terminate `git checkout` argv with `--`.
- Drop `git log` range values that start with `-` and terminate the argv
with `--` so a range can never be reinterpreted as `--output=<file>`.
- Fix getDefaultBranch always falling back to origin/main: the promisified
exec lacked `encoding: 'utf8'`, so stdout was a Buffer and .trim() threw.
- Parameterize ghErrorMessage so `gh pr create` timeouts name the right
command and duration; sanitize workspace paths in PR-create errors.
- GitDialog: guard doCommit against double-submit (button + keyboard), and
strip only a known remote prefix from the PR base so local branches with
"/" are not mangled; use theme tokens for commit button/success colors.
- BranchPickerPopover: guard checkout/new-branch behind busyAction, reset
inline-input text on reopen, and hide the commit action when unavailable.
Adds regression tests for the checkout/branch validation and the git log
range guard.
* fix(web-shell): address review feedback on branch picker PR (#7731)
- Fix Commit+Push error masking: split try/catch so push failure
reports alongside the successful commit SHA
- Replace hardcoded screenshot path with captureScreenshot harness
- Replace silent if-isVisible skip with explicit assertion in visual test
- Add focus-visible style for search input accessibility
- Fix CSS specificity for active PR tab hover state
- Add viewChanges to actionsVisible search filter
- Wrap toggleSection in useCallback to avoid unnecessary re-renders
- Add windowsHide: true to getDefaultBranch subprocess
- Fix trailing slash handling in mockDaemon git action routing
- Add git methods to top-level client mock in tests
- Remove dead branchPicker.commitSuccess i18n key
- Remove dead .actionShortcut CSS class
- Show generation failure feedback in commit message placeholder
* fix(web-shell): address review feedback on branch picker PR (#7731)
- Add workspace trust checks to bound git branch routes
- Use workspace-scoped client in BranchPickerPopover (fixes wrong-workspace mutation)
- Add branch name validation and -- terminator to gitCreateBranch
- Filter refs/remotes/*/HEAD from branch listings
- Force LC_ALL=C for reflog parsing (non-English locale fix)
- Narrow 'could not resolve' error regex to avoid DNS false positives
- Add range validation to git log (reject path traversal)
- Fix commit+push error i18n (dedicated key instead of concatenation)
- Add i18n for BranchSelect component strings
- Fix commit tab ARIA attributes
- Add onBranchChanged callback to handlePush
- Add btw to mockDaemon isDaemonPath regex
- Add workspace_github_prs to visuals spec capabilities
- Add -- terminator regression test
- Remove docs/pr-assets/ from repo
* fix(web-shell): address review feedback on branch picker PR (#7731)
* fix(cli): reject dash-prefixed branch name with 400 in branch route (#7731)
* fix(web-shell): address review feedback on git branch picker (#7731)
Security:
- Clear GIT_DIR/GIT_WORK_TREE/GIT_COMMON_DIR/GIT_INDEX_FILE from git
subprocess env to prevent repository redirection
- Add strict mutation gate to all POST git branch routes
- Add generation guard to qualified write routes
- Fail closed on invalid ?cwd= in mutation routes (resolveContainedCwdOrFail)
- Reject wrong-typed startPoint, fetchOnly, rebase, and PR options with 400
Correctness:
- Filter remote symbolic refs (origin/HEAD) by %(symref) instead of /HEAD
name suffix, preserving valid branches like feature/HEAD
- Add git rev-parse --git-dir probe so non-git dirs get 404 instead of
empty available:true
- Push preserves existing upstream; only adds --set-upstream when unset,
resolving the remote from branch config or the sole configured remote
- git commit -a replaced with git add -A + git commit so untracked files
displayed in the UI are included
- Always pass --body to gh pr create to prevent interactive prompts
- getDefaultBranch returns null instead of fabricating origin/main
- Memoize workspaceByCwd client in BranchPickerPopover to fix infinite
render loop
- Move sessionId to a ref in GitDialog effects to prevent self-abort
- Bound commit-message prompt to fit /btw 4096-char limit
- Mark all platforms as unverified in PR template (no fabricated ✅)
- Guard PR auto-fill effect against wiping user edits on reconnect
Accessibility:
- Add tabIndex and onKeyDown to commit-mode tab span
- Add aria-label to BranchSelect trigger and search input
Cleanup:
- Remove dead CSS (.prInputSmall, .prSelect)
- Remove 9 unused i18n keys
- Add ^ to git log range validation regex
- Add busyAction guard to handlePush/handlePull
- Add unit tests for gitCommit, gitPull, and route input validation
* fix(web-shell): address review feedback on git branch picker PR (#7731)
- Change commit tab from <span> to <button> for keyboard accessibility
- Move setCommitMsg('') to success-only branches so the message is
preserved when push fails after a successful commit
- Add mutate middleware and generationGuard to PR creation route,
matching all other POST mutation routes
- Set genFailed when session resolution returns undefined so the user
sees the failure indicator instead of a silent empty textarea
- Make PR number nullable when URL regex does not match instead of
returning a misleading 0
- Add LC_ALL=C and LANG=C to gitEnv() so for-each-ref upstream track
parsing is locale-independent
- Validate setUpstream and force as booleans in handlePush, matching
the existing validation in handlePull
- Add missing workspaceCwd and available fields to test mocks
- Use stable data-web-shell-git-branch attribute in e2e selector
* fix(web-shell): address R5 review feedback on git branch picker PR (#7731)
- Classify git errors on stdout+stderr instead of err.message to fix
false-positive no_upstream on every push failure and dead
nothing_to_commit classifier
- Sanitize workspace paths and cap error message length in sendGitError
- Fix remote branch checkout to strip remote prefix so git DWIM creates
a local tracking branch instead of detaching HEAD
- Restore keyboard accessibility on composer branch chip (span → button)
- Trim startPoint in handleCreateBranch before forwarding to git
- Return bare branch name from getDefaultBranch (strip remote prefix)
- Fix i18n shortcut hint to show ⌘/Ctrl+Enter for cross-platform
- Update aria-label to reflect git management menu, not just changes
- Add available: true to mockDaemon gitDiff default payload
- Add regression tests: upstream preservation, sole remote resolution,
strengthened fetch-only with divergent remote commit
- Add aria-expanded assertion to sidebar picker test
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(web-shell): address R6 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R7 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R8 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R9 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R10 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R11 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R12 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R13 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R14 review feedback on git branch picker PR (#7731)
* fix(web-shell): address R15 review feedback on git branch picker PR (#7731)
- Validate localName derived from remote-tracking ref to prevent option
injection (e.g. origin/-f → git checkout -f)
- Add gitCwd prop to BranchPickerPopover and pass it to all git SDK calls
so worktree sessions target the correct directory
- Add symlink-escape and non-existent-path tests for resolveContainedCwdOrFail
- Pin initial branch name in makeRepo() with git init -b master
- Add gitPull merge and rebase integration tests
* fix(web-shell): address git branch picker review feedback (#7731)
* fix(web-shell): address R6 review feedback on git branch picker PR (#7731)
* fix(web-shell): address review feedback on git branch picker PR (#7731)
- Strip GIT_CONFIG_GLOBAL/SYSTEM/NOSYSTEM in gitEnv to prevent
inherited config redirection (consistent with extension/github.ts)
- Pass gitCwd to workspaceGitBranches in GitDialog loadPrBranches
so worktree sessions fetch branches from the correct repository
- Add aria-expanded to collapsible branch section headers
- Add happy-path tests for PR create (201) and default-branch (200)
routes, including the null fallback to origin/main
* fix(cli): add sendGenerationClosedError to POST routes and cover untested branches (#7731)
* fix(web-shell): address review feedback on branch picker and PR creation (#7731)
- Refresh branch list after push/pull to avoid stale ahead/behind counts
- Add pre-flight check for unpushed branches before PR creation
- Fix base branch prefix stripping when branch list is unavailable
- Cap PR body file list at MAX_SUMMARY_CHARS to bound model prompt size
- Add qualified route tests: trust guard, input validation, cwd containment
* fix(web-shell): hoist MAX_SUMMARY_CHARS to module scope for PR body generation (#7731)
* fix(web-shell): address review feedback for git branch picker (#7731)
- Hoist onOpenCommit to useCallback to fix App.test.tsx prop stability test
- Keep commit tab visible after navigating away (startedInCommit ref)
- Add onClick handler to commit tab for navigation back to commit view
- Fix branch-prefix strip mangling local branch names containing '/'
- Update sessionIdRef after force-creating a stale session replacement
- Pin core.hooksPath in test makeRepo for reliable rollback tests
- Add test asserting --force-with-lease is used for force pushes
* fix(web-shell): target the worktree for sidebar commits and harden git actions (#7731)
Scope the sidebar commit dialog to the active session's worktree checkout
(matching the composer path) so linked-worktree sessions commit to the right
checkout, guard PR creation against a double-click race, and surface an error
when an invalid branch name is submitted. Adds focused coverage for the branch
picker action wiring and the git branch route validation paths.
* fix(web-shell): address review feedback for git branch picker (#7731)
---------
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
* feat(channels): dispatch GitHub notifications by reason
Route each GitHub notification by notification.reason into one of five
lanes, instead of dispatching every new comment regardless of trigger:
- mention: only dispatch comments that actually @ the bot (noise reduction)
- review_requested (PR): fetch PR meta via pulls.get and dispatch a
review-specific prompt, even with no new comments
- assign: fetch issue meta and dispatch a triage-specific prompt
- author/comment: aggregate the window's new comments into one check-and-
respond prompt
- other reasons: generic fallback (current behavior)
Add cursor dedup via dispatchedComments (by comment node_id) and
dispatchedNotifications (by notification id), surviving a
markNotificationsAsRead failure that leaves the cursor un-advanced.
Closes#7807
* fix(channels): mark review_requested/assign envelopes as mentioned
GroupGate defaults to requireMention: true, which silently drops
isMentioned:false envelopes as 'mention_required'. The review_requested
and assign lanes are explicit directed triggers — the bot was asked to
review or assigned — equivalent to a mention, so set isMentioned: true
so they pass the gate instead of being inert on the documented default
config.
Addresses review Critical on #7826.
* fix(channels): resolve github routing review comments
* fix(channels): dedupe github meta lane comments
* fix(channels): conditional assign framing for PR threads
The assign route already detected PR threads to use pulls.get, but the
trigger framing text always read 'assigned to this issue' even for PRs.
Make it conditional so PR assignments read 'assigned to this pull request'.
* fix(channels): dedup meta lane dispatch inputs
* fix(channels): simplify GitHub reason dispatch
* fix(channels): respect mention gate for github aggregate lane
* fix(channels): truncate aggregate comment bodies by code points
Match the code-point-aware truncation already used for meta-lane bodies
so a supplementary-plane emoji at the MAX_COMMENT_CHARS boundary is not
split into a lone surrogate.
* fix(channels): harden GitHub dispatch failures, event window, and framing (#7826)
- Classify deleted/transferred subjects (404/410) as terminal so a single
dead notification is logged and skipped instead of wedging the batch's
mark-read and cursor advance every poll.
- Widen the review_requested/assign event search to the newest ~100 events
by merging the preceding page when the last page is partial, instead of
inspecting only the last page (which can hold a single event).
- Move the aggregate lane's untrusted-data warning to the head of the prompt
text so it precedes the comment text it describes (metadata is appended
after text by ChannelBase).
- Add regression tests: permanent-failure two-poll advance, terminal 404
no-retry, multi-page event search, prompt caps, and the no-actor guard.
* fix(channels): drop lastReadAt filter in findMetaTrigger, add review coverage (#7826)
* fix(github): keep aggregate and meta windows bounded
* fix(channels): apply windowSince lower bound in findMetaTrigger (#7826)
* fix(channels): bound retry wedge, compute aggregate isMentioned, fix pairing pre-filter (#7826)
* fix(github): record dispatch before handler
* fix(github): persist skipped notifications
* fix(github): close dispatch retry loss cases
* test(github): cover cursor trim and meta floor validation
* fix(github): simplify notification reason dispatch
* fix(github): preserve batched dispatch comments
* fix(github): restore direct event dedup
* fix(github): preserve directed mention context
* fix(github): keep review fixes scoped
* fix(github): preserve delayed direct triggers
---------
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>