mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-01 12:24:51 +00:00
3758 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
97aaa3808d
|
feat(review): disclose a zero-finding Approve on a non-trivial diff as low signal (#7987)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Every gate compose-review enforces proves the agents READ the diff — coverage proves the transcripts, anchors prove the quotes — but none proves the review could tell good code from bad. Dogfooded: a weak-model run drafted nothing from its entire roster on a diff where stronger same-condition runs found a verified blocking Critical, and compose printed a bare, confident "Verdict: Approve". A reader takes that as evidence of quality when it is only absence of signal — the most dangerous shape a review verdict can have. Disclose it, deterministically. When the composed event is APPROVE and the plan's srcDiffLines — the same field the review topology is chosen from, with tests, docs and generated files excluded by construction — exceeds 100, the verdict line names the shape: Verdict: Approve — low signal: none of the 11 review agents reported a finding on a non-trivial diff (155 source diff lines) The event never moves on it: a cap would punish every genuinely clean diff, and nothing was in fact found. Docs-only and typo-class diffs keep their bare Approve — there, finding nothing is the expected outcome. The floor sits well past the typo-fix class (a tiny edit scattered one line per hunk stays under it) and at a fifth of the smallest diff the topology gate calls big. Both numbers in the line are the run's own: the roster the plan required (all on record at APPROVE, or coverage would have capped) and the plan's source-line count. Co-authored-by: verify <verify@local> |
||
|
|
566d2c1279
|
fix(cli): stop resize repaint from causing scroll storm (#8009)
* fix(cli): stop resize repaint from causing scroll storm (#8004) Remove the useResizeSettleRepaint -> refreshStatic wiring that wrote clearTerminal (destroying scrollback) and remounted <Static> on every settled resize, re-emitting all conversation history in 50-item chunks. Ghostty's panel-toggle animation exceeds the 200ms debounce, triggering multiple settle-repaint cycles per toggle -- visible as continuous scrolling/flickering. Ink's dynamic region already re-renders on width changes via useTerminalSize; modern terminals handle scrollback reflow natively. The full remount is no longer necessary. The now-unused hook and its test are removed (no remaining callers). * test(cli): guard resize no-repaint contract against settle-time regression (#8004) * test(cli): make resize settle regression test non-vacuous (#8004) The previous test used rerender() which remounts the tree via ink's ErrorBoundary (measureElement returns undefined → layout effect throws → tree unmounted), so the settle debounce never fired and the test passed regardless. Rewrite to keep the tree alive (measureElement mock returns a real value) and deliver width changes to the same mounted instance via a listener pattern. Mutation-verified: fails when the removed useResizeSettleRepaint hook is restored. * chore(cli): remove dead useResizeSettleRepaint from eslint legacy filenames (#8004) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
c97026040e
|
feat(channels): add pairing approval management API (#8045)
* feat(channels): add pairing approval management API * fix(sdk): expose pairing approval types Re-export the new approval and revocation types from the public SDK entry, and pin the qualified workspace DELETE request body in regression coverage. --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
3bcb4d3412
|
fix(channels): disable native cron in daemon sessions (#8034)
* fix(channels): disable native cron in daemon sessions * test(cli): cover Cron restore routing --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
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>
|
||
|
|
aac663f28a
|
feat(hooks): add security.allowPrivateNetworkHooks to bypass SSRF range checks for trusted scopes (#7968)
* feat(hooks): add security.allowPrivateNetworkHooks to bypass SSRF range checks for trusted scopes HTTP hooks hard-block all private/link-local address ranges via ssrfGuard, which makes them unusable in platform-managed environments where the hook receiver is a first-party, VPC-internal endpoint (e.g. an internal API gateway resolving to 172.16.0.0/12). Add an opt-in setting, security.allowPrivateNetworkHooks (default false), that skips the SSRF IP-range checks in urlValidator.isBlocked (literal IPs) and validateResolvedHost (literal + post-DNS-resolution paths). Security properties: - Honored only from User/System/SystemDefaults scopes; the value is stripped from Workspace settings during the merge (with a startup warning), so a cloned repository can never self-grant the bypass. - BLOCKED_HOSTS (169.254.169.254, metadata.google.internal, ...) remains blocked even when the flag is on. - Default false keeps every code path byte-for-byte compatible with current behavior; bare/safe mode forces it off. * fix(hooks): enforce metadata endpoint blocklist regardless of allowPrivateNetworkHooks Address review findings on #7968: with the flag on, cloud metadata endpoints were reachable through gaps in the relaxed checks. - ssrfGuard: add METADATA_IPS (169.254.169.254, 100.100.100.200) and isMetadataAddress(), which normalizes IPv4-mapped IPv6 forms (::ffff:a9fe:a9fe, ::ffff:6464:64c8, ...) via the existing extractMappedIPv4/expandIPv6Groups helpers. - urlValidator.isBlocked: BLOCKED_HOSTS matching and the literal-IP isMetadataAddress check now run unconditionally; only the general range check (isBlockedAddress) is relaxed by the flag. - httpHookRunner.validateResolvedHost: no longer returns early with the flag on — DNS resolution still runs and resolved addresses are checked against isMetadataAddress, so a hostname resolving to a metadata endpoint is blocked. DNS failures still defer to fetch, as before. - settings warning text now lists User/System/SystemDefaults, matching the schema and docs. - docs: precise wording — the flag relaxes only range checks; metadata endpoints stay blocked in all serialized forms and after DNS resolution. The flag now opens RFC1918/CGNAT/link-local ranges only; cloud metadata endpoints (169.254.169.254, 100.100.100.200 in any form, plus the BLOCKED_HOSTS hostnames) are unreachable in every configuration. --------- Co-authored-by: 欢伯 <ri.xur@alibaba-inc.com> |
||
|
|
7ce9a4fac7
|
fix(cli): MCP prompt completion no longer blocks Enter for optional params (#7995)
* fix(cli): MCP prompt completion no longer blocks Enter for optional params (#7991) The completion handler treated all unused arguments (required + optional) as completion suggestions with auto-appended `="`, making optional params look mandatory. After selecting a prompt name, pressing Enter would auto-append `--input="` instead of executing the prompt. Two changes: 1. Completion handler now parses named args directly instead of using parseArgs() (which returns Error on missing required args — the normal state during tab completion). 2. Only required unused args are suggested as completions. When all remaining unused args are optional, the completion list is empty so Enter executes the prompt with defaults. * fix(cli): restore optional and positional MCP prompt completion discovery (#7991) * fix(cli): share named-arg regex and cover mixed MCP prompt completion (#7991) * fix(cli): suggest optional args mid-keystroke and test multi-word positionals (#7995) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
97ed1da3c4
|
fix(core): auto-record artifact file writes (#7914)
* fix(core): auto-record write_file artifacts * test(core): cover all inferWorkspaceArtifactKind kind groups (#7914) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(core): cover .htm extension in artifact kind inference (#7914) * fix(core): clarify artifact registration guidance * fix(core): keep artifact extension kinds in sync * fix(core): type workspace write artifacts * fix(core): guard artifact title safety and fill ipynb mimeType (#7914) * fix(core): align artifact guard with store rules and fix Windows test (#7914) * fix(core): share artifact length limits and pin guard clauses (#7914) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.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> 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> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
bfd64114bf
|
fix(cli): reserve Ctrl+Shift+C for terminal copy, not quit/clear (#8011)
* fix(cli): reserve Ctrl+Shift+C for terminal copy, not quit/clear
Ctrl+Shift+C is the standard terminal copy shortcut. The QUIT and
CLEAR_INPUT bindings matched it because they only constrained ctrl
and left shift unspecified, which the matcher ignores. In Kitty-
protocol terminals the escape-hatch check also caught Ctrl+Shift+C
(ESC[99;6u) since it tested ctrl+name without excluding shift.
Add shift: false to both bindings and !key.shift to the escape-hatch
condition so Ctrl+Shift+C passes through to the terminal for copy.
Plain Ctrl+C (shift undefined or false) is unaffected.
Fixes #8006
* test(cli): guard Ctrl+Shift+C paste escape-hatch against !key.shift mutation
The existing Kitty Ctrl+Shift+C test passed identically with or without
the `!key.shift` condition in the Ctrl+C escape hatch, so a regression
making Ctrl+Shift+C clear a stuck paste would ship undetected. Add a
test that enters a stuck keypress-level paste and asserts Ctrl+Shift+C
is buffered as paste content rather than dispatched, which fails when
`!key.shift` is removed.
* fix(cli): remove dead !key.shift escape-hatch guard (#8011)
The KeypressContext.tsx escape-hatch guard added in
|
||
|
|
2ce9da85bd
|
fix(review): preflight free disk before build-test installs and builds (#7986)
Dogfooded on a live review: with ~2.7G free, `npm ci` on this monorepo ran 33 seconds and died on ENOSPC — and the now-full disk went on to fail every agent scheduled after this command. Verification and reverse-audit agents could not even spawn, so a Critical that nine agents independently reported ended the run capped at Comment because nothing could verify it. A disk a command fills is not a failure that stays contained to that command. build-test already treats "cannot finish in time" as an infrastructure result: the deadline skips the command and discloses it, and the agent brief routes such notes to informational, never a finding. "Cannot fit on the disk" is the same class of result, discoverable before the command runs instead of 33 seconds into it. Add a free-disk preflight at two floors: 3 GiB before `npm ci` (the installed tree is ~1.4G and npm stages cache and temp writes on the same filesystem while materialising it) and 1 GiB before the build phase (dist/ and tsbuildinfo write far less; the floor exists so a compile cannot be the thing that fills the disk). Two floors deliberately: a warm tree at ~2G free skips the install anyway and must still be allowed to build. Below a floor the command is skipped with the same disclosure shape as a deadline skip. Where statfsSync is unavailable the preflight passes — it exists to prevent failures, not to invent them. Co-authored-by: verify <verify@local> |
||
|
|
cc617e6707
|
fix(daemon): distinguish draining channel worker diagnostics (#7932)
Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com> |
||
|
|
9bbd394cb2
|
fix(cli): map Kitty Super (Command) modifier to meta (#7996)
* 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> |
||
|
|
4615f84d73
|
fix(serve): allow bounded reads of large text files (#7947)
* 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>
|
||
|
|
4c11e21bcd
|
fix(cli): prevent SGR mouse events from being swallowed as paste on Windows (#7988)
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> |
||
|
|
81f1ffb94c
|
fix(cli): correct hardware cursor off-by-one in fullscreen mode (#7998)
* 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> |
||
|
|
5ce832e497
|
feat(core): connect Goal v3 to the turn engine (#7895)
* 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> |
||
|
|
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> |
||
|
|
dade3ab334
|
feat(web-shell): add git branch picker, commit dialog, and create PR flow (#7731)
* 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> |
||
|
|
6fb63b73e1
|
fix(review): recover the resolved effort when --effort is not re-threaded (#7855)
* fix(review): recover the resolved effort when --effort is not re-threaded The capture commands (capture-local, plan-diff, fetch-pr) record the review effort as plan.effort, which every downstream consumer — roster, check-coverage, compose-review — reads. They learn it from --effort <level>, and the skill asks the orchestrator to pass the level parse-args resolved. But that is a value the model must copy from one file into a flag, and it does not reliably happen: a `/review --effort medium` local run had the orchestrator omit the flag, so plan.effort was absent and the roster safe-expanded to the FULL set — the user asked for the reduced medium roster and silently got every agent (6a/6b/6c included). Close the gap deterministically. A new resolveEffort() prefers an explicit --effort, and otherwise reads the level parse-args already wrote to its conventional report (.qwen/tmp/qwen-review-parse-args.json). When neither is available it returns undefined and the roster fail-safes to the full set exactly as before, so a missing report never reduces coverage, and a malformed level is ignored rather than trusted. * refactor(review): dedupe effort resolution per review feedback - Share one EFFORT_LEVELS set: export it from parse-args and import it in effort.ts, so a new level cannot be added to one set but not the other. - Collapse the three identical effort-spread IIFEs into planEffortField(). - Isolate CWD in plan-diff.test.ts (matching capture-local.test.ts) so resolveEffort's CWD-relative report read cannot pick up a stale file and fail the "omits effort" case. * test(review): pin PARSE_ARGS_REPORT value and plan-diff effort fallback (#7855) * test(review): dedupe seed helper, cover fetch-pr effort, trace resolution (#7855) * fix(review): spread planEffortField last for consistent effort precedence (#7855) --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> |
||
|
|
20f9f08ce5
|
feat(channels): expose loop tools in daemon sessions (#7891)
* feat(channels): expose loop tools in daemon sessions * fix(serve): preserve fast-path bundle boundary --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
ad1b5f3de1
|
fix(cli): default to virtualized terminal history (#5738)
* fix(cli): default to virtualized terminal history * fix(cli): remove redundant alternate screen exit handler * fix(cli): keep non-interactive output off VP mode * fix(cli): stabilize VP tests in CI environments * test(cli): resolve SDK daemon source in vitest * fix(cli): normalize CI env checks for VP mode * fix(cli): keep default VP mouse interactions enabled * fix(cli): align VP mouse behavior with runtime state * fix(cli): stabilize virtual viewport runtime state * test(cli): cover virtual viewport fallbacks * docs(cli): clarify virtual viewport requirements --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
0afb48b1ea
|
fix(safe-mode): preserve caller-supplied top-tier MCP servers (#7827)
* fix(safe-mode): preserve caller-supplied top-tier MCP servers Safe mode is meant to distrust LOCAL/ambient state (settings.json, extensions, project .mcp.json) so a user can isolate which local customization is misbehaving. It was also unconditionally dropping topTierMcpServers -- the caller-supplied servers from an ACP session/new's mcpServers field or --mcp-config -- which are an explicit, per-invocation argument, not ambient local state. The real gate turned out to be in packages/core/src/config/config.ts's Config.getMcpServers() (the accessor mcp-client-manager.ts actually reads for discovery), not just the mcpServers assembly in loadCliConfig -- fixed both so the raw field and the accessor agree. Also guarded the hot-reload path (hot-reload.ts) with the same bare/safe-mode check, so a live settings.json edit can't smuggle local servers into an already-running bare/safe-mode session. Fixes #7819 * docs: clarify safe-mode's MCP servers note distinguishes local vs caller-supplied Per CONTRIBUTING.md guideline 5 (docs for user-facing changes). * fix(safe-mode): apply allowedMcpServers to top-tier servers, skip pendingMcpServers I/O under safe mode Address Copilot's review of PR #7827: - getMcpServers() now runs the safe-mode top-tier map through the same allowedMcpServers filter as the non-safe-mode path -- safe mode is not an exemption from a session's own --allowed-mcp-server-names upper bound (High severity finding, confirmed real). - Re-added safeMode to pendingMcpServers' skip condition in loadCliConfig. Functionally a no-op either way (top-tier servers are never gated, #4615), but skips getMcpApprovals' local file read entirely under safe mode instead of doing a no-op read -- safe mode shouldn't touch local/ambient state at all, not even harmlessly (Medium severity finding). * fix(safe-mode): actually run MCP discovery for surviving top-tier servers Config.getMcpServers() reporting a top-tier server as configured isn't enough on its own -- something has to actually connect to it and register its tools with the model. That's a separate gate in initialize(): startMcpDiscoveryInBackground() was skipped outright whenever isSafeMode() was true, written back when getMcpServers() always returned {} under safe mode (so skipping discovery was a harmless no-op). Left unpatched after the earlier fix, a caller-supplied top-tier server survives getMcpServers() but is never actually discovered/connected -- confirmed live against a real ACP session before this commit: the agent reported the tool as "not configured" even though Config.getMcpServers() already returned it. Found by actually running the fix end-to-end against a live ACP session (qwen --acp --safe-mode + a real stdio MCP fixture server) instead of relying on unit tests of Config in isolation. After this commit the same live session correctly discovers and calls the caller-supplied tool. Checks getMcpServers() (not topTierMcpServers directly) so the allowedMcpServers filter still applies -- no discovery is kicked off if the only top-tier server present is filtered out. * fix(safe-mode): also run MCP discovery for surviving bare-mode top-tier servers The discovery-kickoff gate in Config.initialize() special-cased safe mode (skip only when there's nothing to discover) but left bare mode's half of the same guard unconditional (!this.getBareMode()), even though loadCliConfig feeds top-tier MCP servers into bare mode's mcpServers assembly exactly the way it does safe mode's topTierMcpServers field. A bare-mode session with a caller-supplied server (qwen --bare --mcp-config, or ACP session/new under bare mode) had that server reported as configured by getMcpServers() but never actually connected/discovered — the same stranded-server regression already fixed for safe mode in this PR, just the bare-mode twin of it. Found by an automated review pass on PR #7827 after the safe-mode fix had already landed. Added the same three-case regression coverage (present / nothing supplied / filtered out by allowedMcpServers) mirroring the existing safe-mode tests, using mcpServers (not topTierMcpServers) since bare mode's "local sources dropped" guarantee lives entirely in the CLI-layer assembly, not a core-level short-circuit. * refactor(safe-mode): simplify the bare/safe discovery-gate condition (!bare || has) && (!safe || has) reduces by distributivity to !(bare || safe) || has, so factor the has-servers check into a single hasMcpServers computed once, instead of calling getMcpServers() (which allocates and filters) twice. Suggested by an automated review pass on PR #7827, commit 25ddf8b. No behavior change — same 23 discovery-gate tests pass unmodified. * fix(safe-mode): stop reading settings.mcp.allowed/excluded under safe mode allowedMcpServers/excludedMcpServers assembly in loadCliConfig() only guarded the settings-sourced branch with `!bareMode`, missing `!safeMode` — so a local settings.json mcp.allowed/excluded list (LOCAL/ambient state, same category as settings.mcpServers itself, which safe mode already drops) was still read under safe mode. Combined with getMcpServers()'s own allowedMcpServers filter (added earlier in this PR for the --allowed-mcp-server-names case), a settings.json mcp.allowed list narrower than the caller's own top-tier servers would silently filter them back out — defeating the guarantee this PR exists to provide, via the filter's source rather than the mcpServers map directly. The argv.allowedMcpServerNames branch is unaffected: that's an explicit per-invocation argument, not local state, so it still applies under safe mode same as topTierMcpServers itself. Found by an automated review pass (doudouOUC, CHANGES_REQUESTED) on PR #7827. Regression test confirmed red before the fix (session-supplied server silently filtered out) and green after. Full targeted vitest run (packages/cli config/: 1018 passed, same 3 pre-existing Windows-only extension-file-watcher failures as before, unrelated), tsc --noEmit, eslint, prettier --check all clean. * fix(safe-mode): stop reading settings.mcp.allowed/excluded on hot-reload too Same class of bug as the previous commit's loadCliConfig fix, found by an automated review pass on the SAME PR: recomputeMcpGating (hot-reload.ts) reads settings.merged.mcp.allowed/excluded unconditionally, with no bare/safe guard of its own. registerMcpHotReload's existing bare/safe guard only covered the servers map (`next`), not the admission lists computed right after it — so a live settings.json edit narrowing mcp.allowed during an already-running safe/bare session would flow straight into setAllowedMcpServers and silently filter the caller's top-tier server out of getMcpServers() mid-session. Same stranded-server outcome as the boot-time bug, reached through the gating list's SOURCE instead of the mcpServers map. Fix: under bare/safe mode, skip recomputeMcpGating entirely and build the gating directly from only the CLI --allowed-mcp-server-names bound (explicit, per-invocation, not local state — same treatment as topTierMcpServers itself); excluded/pending are irrelevant once nothing but the never-gated top-tier servers can be present. Regression tests (safe mode + bare mode) confirmed red before the fix (setAllowedMcpServers called with the settings-sourced list) and green after (called with the CLI bound, undefined here). Full targeted vitest run (packages/cli config/: 1020 passed, same 3 pre-existing Windows-only extension-file-watcher failures as before this PR touched anything, unrelated), tsc --noEmit, eslint, prettier --check all clean. * fix(safe-mode): stop reading settings.mcp.allowed/excluded on ACP reload too Third instance of the same bug class found by an automated review pass on this PR: reloadWorkspaceMcpDiscovery (packages/cli/src/acp-integration/ acpAgent.ts) — the ACP control-endpoint reload path (workspaceMcpReload), distinct from registerMcpHotReload's settings-file-watcher path fixed in the previous commit — called assembleMcpServers(settings.merged.mcpServers, ...) and recomputeMcpGating(settings, ...) unconditionally, per live Config in liveConfigs, with no bare/safe guard. A workspaceMcpReload request against an already-running safe/bare session would fold local mcpServers/mcp.allowed/excluded back in, silently stranding or filtering the caller's own top-tier server mid-session — same outcome as the two prior fixes, reached through a third independent reload path. Fix: per-config (liveConfigs holds a Set of potentially differently-moded Configs — the base config, active session configs, and the discovery config), skip assembleMcpServers/recomputeMcpGating under bare/safe mode and build servers/gating directly from that config's own getTopTierMcpServers()/getCliAllowedMcpServerNames() — same treatment as the other two fixes. Regression test (packages/cli/src/acp-integration/acpAgent.test.ts) confirmed red before the fix (settings-sourced 'local' server leaked into reinitializeMcpServers alongside the caller's 'probe') and green after. Also added getBareMode/isSafeMode mocks (defaulting false) to the two pre-existing Config-shaped mocks in this describe block that didn't have them — reloadWorkspaceMcpDiscovery now calls these unconditionally per config, which would otherwise throw "not a function" against any mock missing them, even for a normal-mode test. Full targeted vitest run (packages/cli/src/acp-integration/acpAgent.test.ts: 318 passed), tsc --noEmit, eslint, prettier --check all clean. * test(safe-mode): add bare-mode counterpart for the workspaceMcpReload guard Suggested by an automated review pass on PR #7827: the previous commit's regression test for reloadWorkspaceMcpDiscovery only exercised isSafeMode: true, leaving the bare-mode half of config.getBareMode() || config.isSafeMode() unverified at this layer — unlike the hot-reload.ts tests, which already cover both modes for both the servers map and the admission lists. A future change narrowing that guard to isSafeMode() only would go undetected here. Confirmed red before the fix (temporarily reverted acpAgent.ts to the prior commit): settings-sourced 'local' leaked into reinitializeMcpServers alongside the caller's 'probe', same as the safe-mode case. Green with the fix restored. Full targeted vitest run (acp-integration/acpAgent.test.ts: 319 passed), tsc --noEmit, eslint, prettier --check all clean. |
||
|
|
788e5cd3a8
|
feat(core): add ARMS session user ID (#7921)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f7fd831003
|
fix(cli): remove redundant 'Read file' prefix from @mention tool card (#7902)
* fix(cli): remove redundant "Read file" prefix from @mention tool card
The @mention file-read tool card set its description to
"Read file <name>", which duplicated the display name ("Read")
when rendered as "{displayName} {description}", producing
"Read Read file README.md".
Change the description to "@<name>" — matching the @mention
syntax the user typed, and consistent with how normal read_file
tool calls show just the filename in their description.
* test(cli): assert @mention tool card description format (#7902)
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
|
||
|
|
0be42b782d
|
fix(cli): add polling fallback for git branch name display (#7830)
* fix(cli): add polling fallback for git branch name display (#7828) fs.watch on .git/logs/HEAD is unreliable on NFS, FUSE, Docker overlay, and some Linux filesystems — events can be silently dropped with no recovery path, leaving the footer branch name stale indefinitely. Added a 5-second polling fallback in useGitBranchName that calls resolveBranchName and updates the state only when the value changes. The timer is unref'd so it doesn't keep the process alive. The existing fs.watch mechanism is preserved for immediate updates when it works correctly. * test(cli): cover the git branch polling fallback (#7830) * refactor(cli): use idiomatic timer.unref?.() in branch poller (#7830) * fix(cli): order concurrent branch refreshes by generation (#7830) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
4703cc5432
|
fix(serve): Release managed session writer locks on shutdown (#7812)
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
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
* fix(serve): release managed writer locks on shutdown Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): address shutdown review feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): release writer locks after flush failures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): harden managed shutdown recovery Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
6f739c7a46
|
fix(cli): hide stale sticky todos from previous turns (#7900)
* fix(cli): hide sticky todo panel when a new turn starts PR #7062 hid the sticky panel when streamingState is Idle, but the panel resurfaces with stale todos from a previous turn as soon as the user sends a new message (state → Responding). This is confusing — the user sees in-progress indicators for work that already finished in an earlier turn. Add a turn-boundary check in getStickyTodos: if a user message exists after the todo snapshot in history, the snapshot belongs to a previous turn and the sticky panel returns null. Fixes #7061 * test(cli): pin sticky-todo turn boundary for local slash commands (#7061) --------- Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
9461aa860d
|
fix(core): bridge tool-result images for text-only models (#7484)
* fix(core): bridge tool-result images for text-only models * test(vision-bridge): pin tool-result full-turn guards and surface bridge errors (#7484) * test(cli): cover drain-item model override conflict rejection (#7484) * test(cli): cover stop-hook full-turn model persistence * fix(core): disclose tool image routing * fix(cli): type restored vision notices * test(cli): fix stop-hook vision fixture --------- 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> |
||
|
|
b3873571aa
|
fix(daemon): harden Todo Stop Guard continuations (#7821)
* fix(daemon): harden Todo Stop Guard continuations Linearize Guard continuation ownership across bridge consumers, preserve queued input across failure paths, and tighten session lifecycle and lineage handling. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): yield to event loop in waitForActiveTurnsToSettle (#7821) * fix(channels): discard retired ACP sessions Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): preserve superseded continuation state Preserve unsent continuation results across prompt supersession and cancellation, and keep closing sessions from restoring stale FIFO priority. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- 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> |
||
|
|
090b213ad8
|
feat(web-shell): add native workspace folder picker (#7849)
* feat(web-shell): add native workspace folder picker * fix(serve): harden native directory picker and add coverage (#7849) Add a 5-minute timeout to each native picker subprocess and to the webui action so a dismissed dialog cannot leave an orphaned GUI process, and distinguish a headless Linux "cannot open display" failure from a deliberate zenity cancellation. Cover pickNativeDirectory's platform branches, the route's 501/500 error paths, and the dialog's picker failure path with focused tests. * fix(serve): log directory picker failures to daemon stderr (#7849) * fix(serve): abort directory picker on client disconnect and stagger timeouts (#7849) * test(webui): add unit tests for pickWorkspaceDirectory action (#7849) * fix(serve): set UTF-8 console encoding for PowerShell picker and treat timeout kills as cancels (#7849) * fix(serve): abort directory picker on response close, not request close (#7849) --------- 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 CI <qwen-code-ci@users.noreply.github.com> |
||
|
|
a8ad0bcbd9
|
fix(cli): make /copy <message> <index> select the code block (#7883)
selectCodeBlock seeded `lang` from the first token before the classification loop ran. For a bare number that is wrong: `/copy 1 2` strips the leading 1 as the message index and hands "2" over as the whole selector, so lang became "2" while the loop set requestedIndex to 2. The filter then looked for blocks written in a language called "2", found none, and reported that no code block matched. The argument hint advertises `[N] [<lang>|code|latex|mermaid] [<index>]` with every group optional, so this is a documented form. The seeding is redundant as well as wrong: the loop already assigns lang for any token that is neither a number nor `code`. Same defect and same fix as #7789 in packages/web-shell; @wenshao verified the CLI copy carried it and suggested the follow-up. Filed separately rather than folded into that PR. |
||
|
|
6add598826
|
fix(cli): do not count a partial trailing line when re-opening a split fence (#7875)
countHeadContentLines counted the newlines in the committed head, and when
the head did not end in a newline it returned the raw count -- treating the
unfinished last line as complete. The tail's start-line directive is
computed from that count, so every line number after a mid-line split was
one too high.
splitFencedMarkdown('```js\naaa\nbbb\nccc\n```', 12)
head: '```js\naaa\nbb\n```\n'
tail: '```js qwen-code:start-line=3\nb\nccc\n```'
The tail's first row is `b`, the remainder of source line 2, but the
directive labelled it 3.
A completed line and a partial one leave the same number of newlines
behind, so neither needs a special case: the count is always one less than
the newlines in the head, floored at zero. Splits landing on a line
boundary are unchanged.
An existing test asserted `start-line=2` for a block holding one 100-char
line split at 40. The tail there is the remainder of line 1, so it is
updated to 1.
|
||
|
|
963142ab02
|
fix(cli): make wrapToVisualLines count zero-width characters like its sibling (#7873)
wrapToVisualLines and sliceTextByVisualHeight both measure visual rows at a
given width, but only sliceTextByVisualHeight clamps the per-character
width with Math.max(..., 1). `string-width` returns 0 for TAB, ZWJ and
combining marks, so a run of them was charged nothing and collapsed into a
single row:
wrapToVisualLines('\t'.repeat(50), 10).length -> 1
sliceTextByVisualHeight('\t'.repeat(50), 3, 10) -> 5 rows
Same input, same width, two answers. Callers that mix the two -- scroll
offsets, pending-render height -- then disagree with themselves.
Apply the same clamp. Erring high is the safe direction for a terminal:
reserving one row too many costs a blank line, while counting one too few
overflows the region and pushes content off screen.
This aligns on the convention sliceTextByVisualHeight already follows,
including its treatment of combining marks, which both now charge a column.
Making that more precise would mean changing both functions and is left
alone here.
|
||
|
|
3836e7e38c
|
fix(cli): report a genuine $0.00 cost instead of N/A (#7784)
calculateCost ended with 'total > 0 ? total : null', and callers read null as "no cost to report" - the stats table prints N/A and hides the Cost section entirely when no model yields a number. Testing the total conflated a zero cost with an unknown one. A model the user deliberately priced at 0 (a free OpenRouter variant, a local Ollama or LM Studio model) was reported as N/A, and the Cost section vanished when it was the only model in the session. Decide on the pricing and the usage before the arithmetic, then return the total unconditionally. The no-usage case still returns null, which statsCommand.test.ts documents as deliberate. |
||
|
|
b475d1a263
|
feat: Gate session writer lease behind opt-in (#7894)
* feat: gate session writer lease behind opt-in Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(acp): freeze session writer lease per process Snapshot the effective restart-required lease gate from the bootstrap Config and reuse it for every session Config in the ACP process. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(core): align recorder default lease gate Use the effective session writer lease gate when ChatRecordingService is constructed without an explicit writer mode. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
bd2c0b47f1
|
feat(core): add full-resolution image zoom tool (#7809)
* feat(core): add full-resolution image zoom tool * chore(vscode): refresh third-party notices * fix(ui): satisfy zoom image display drift checks * fix(web-shell): translate zoom image tool * fix(core): wire sharp into packaging and load it lazily (#7809) Externalize sharp in esbuild and declare it (plus the @img platform binaries) in the published package's optionalDependencies so an npm-installed CLI resolves the native binding. Import sharp dynamically inside execute() so a missing binding returns a bounded tool error instead of crashing startup during strict tool warmup, and so the module is only loaded when zoom_image actually runs. Also pin the EXIF auto-orientation test with a discriminating fixture and cover the .qwenignore and y1>=y2 validation paths. * fix(core): gate zoom_image at execute time and cap tiny-crop upscale (#7809) Register zoom_image unconditionally and move the image-modality check to execute time so first-run sessions and hot /model switches resolve the tool without re-running initialize(). Cap magnification at 8x so a tiny crop no longer inflates to the full image-token budget. Drop the redundant @img/* platform pins; sharp's own optionalDependencies install the matching binary for each OS/arch. * fix(core): emit zoom_image file telemetry and cover guard branches (#7809) 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-Coder <qwen-coder@alibabacloud.com> |
||
|
|
6930e72970
|
feat(core): persist and replay Goal v3 state (#7815)
* feat(core): persist and replay Goal v3 state * test(goal): pin replay seeding, validation, and rewind boundaries (#7815) * fix(cli): remove dead goalState param, test malformed-state guards (#7815) * test(core): cover recordToolResult provenance override path (#7815) --------- Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
436271067c
|
feat(acp): add session-scoped runtime MCP (#7847)
* feat(acp): add session-scoped runtime MCP * fix(acp): allow slow session MCP mutations * test(acp): await async MCP assertions * fix(acp): allow MCP discovery during session spawn |
||
|
|
9faa8068c3
|
perf(acp): preload providers after session creation (#7767)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
17408f1028
|
feat(hooks): Add submitted prompt provenance (#7762)
* feat(hooks): add submitted prompt provenance Add an optional pre-expansion prompt sidecar for interactive UserQuery hooks while preserving legacy prompt behavior and fail-closed provenance handling across queues, retries, and continuations. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(hooks): harden submitted prompt provenance Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(hooks): tighten submitted prompt provenance Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8785216be5
|
feat(web-shell): add monitor task details (#7817)
* feat(web-shell): add monitor task details * fix(web-shell): align monitor tab title with merged snapshot and reset expansion (#7817) --------- Co-authored-by: ytahdn <ytahdn@gmail.com> |
||
|
|
8a44b1b9f7
|
fix(web-shell): render task notifications as system messages (#7822)
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
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
* fix(web-shell): render background notifications as system messages * fix(web-shell): use basic table rendering by default --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> |
||
|
|
f5248acf21
|
feat(review): script-lint as a deterministic gate — compose-review reads the report, no agent (#7751)
* feat(review): add script-lint — deterministic linters over a diff's executable scripts
A diff's shell — a `.sh` file, a Makefile recipe, a Dockerfile `RUN`, a GitHub
Actions `run:` block — is code, and its bugs (an unquoted `$x` that word-splits,
a `${PIPESTATUS[1]}` read after the array was reset) are the class a reviewer
misses by reading a long YAML and catches by running the checker. Measured: a
model told in prose to "run the workflow scripts" reads instead (0/4 executed).
So the execution is a command, not a request. `qwen review script-lint` reads the
plan, dispatches shellcheck / actionlint / hadolint by file type over the changed
executable files, filters every finding to whether its line is one the diff
changed (`inDiff`), and reports JSON. A linter that is not installed is disclosed
as skipped, never a clean bill. It is not GitHub-specific — shellcheck applies to
shell wherever it appears; actionlint/hadolint are front-ends for two embeds.
This is the command only; the agent, roster requirement and coverage gate that
make it a non-skippable step follow.
* feat(review): make script-lint a required, coverage-gated review step
The script-lint command exists; nothing ran it. This wires it into the review
the way build-test is wired, so a diff that changes an executable script cannot
be certified without its linters having run.
- A `script-lint` agent role (agent-briefs): reads no diff, runs the command,
reports from its JSON. `inDiff` findings are the PR's; `skipped` (a linter not
installed) is disclosed as unreviewed, never clean. Rules are not injected into
it, same as Build & Test — it reports a tool's verdict, not a read.
- agent-prompt welds the exact `qwen review script-lint --plan/--worktree/--out`
into that agent's brief with absolute paths, guarding an absent PR number out
of the --out name — the same treatment, and the same traps avoided, as the
build-test block it sits beside.
- The roster requires the agent whenever the diff carries a file a linter owns by
path (a `.sh`/`.bash`, a `.github/workflows/*`, a Dockerfile) and the review has
a worktree to lint in. Detected by the command's own `pathTool`, so the roster
and the command cannot disagree about what counts. A pure-TS diff does not
require it; a diff-only review (no tree) cannot run it, so does not.
- Coverage needs no new code: it derives missing roles from the roster generically
(`BRIEFS[role].label`), so a required script-lint agent that did not run exit-3s
check-coverage like any other. The role carries its three labels for that.
End-to-end on a crafted diff (a workflow plus a `deploy.sh` with `rm -rf $TARGET`):
the roster requires script-lint, and the command blocks on the SC2086 on the
changed line while disclosing the workflow as skipped where actionlint is absent.
SKILL.md documents the step; tests cover the roster requirement, the brief weld,
and the subcommand registration.
* fix(review): harden script-lint after review — context lines, fail-closed, symlinks, quoting
Addresses the review findings on this PR:
- inDiff was keyed off the plan's hunk ranges, which include git's three context
lines, so a pre-existing diagnostic near a real change was marked this PR's and
could block it. Classify off the diff's added-line ranges (context excluded),
parsed from the diff; fall back to the plan hunks only when the diff is absent.
- The command wrote the report to --out and printed only "Wrote ...", while the
agent's brief (and the roster's generated command, which passes --out) says to
read the JSON it prints. Match build-test: write the file AND always print JSON.
- runTool failed open — every non-ENOENT failure (EACCES, a signal, maxBuffer, an
unexpected status) fell through as empty stdout and became ok:true. Fail closed:
such a run is `errored`, which forces ok:false; ENOENT alone stays "not installed".
- The shebang read slurped the whole file and followed symlinks — a changed
`hang.sh` -> /dev/zero would hang the reviewer. Read only regular files (lstat,
no follow) and only the first block.
- The welded command interpolated plan/worktree/out as bare words; a worktree path
with a space would split. Quote them with shellQuotePath.
- Harden the checker environment: shellcheck --norc + drop SHELLCHECK_OPTS, so a
PR-controlled .shellcheckrc or inherited opts cannot suppress SC2086.
- The roster required the agent for a pure-deletion .sh (a mandatory no-op); gate
on added lines. Drop the Makefile-recipe claim (no detector backs it). Fix the
`ok` JSDoc (info blocks too, not just error/warning).
- Tests: inject the tool runner (no binary needed) to cover actionlint/hadolint
normalisation, the three fail-closed paths, and the context-line classification.
* feat(review): make script-lint a deterministic gate — orchestrator runs it, compose-review is the authority
The review of #7749 landed three architectural findings (#10/#11/#12): the
executable-script lint was run by an AGENT, so its execution rested on the model's
honor system, the model decided each finding's severity, and an uninstalled checker
was only disclosed in prose. All three are the exact "a rule a model is asked to
remember will eventually not be remembered" trap the feature exists to close — and a
measurement on PR #7724 confirmed it: the strongest model's Step-3 agents, given the
diff and the worktree, missed all three execution-confirmed bugs, one attacker
persona walking into a double-execute and declaring it correct.
So take the model out of the gate entirely:
- The orchestrator runs `qwen review script-lint` as a deterministic step (like
presubmit) and writes the report next to the plan. No agent.
- `compose-review` derives the report path from the plan and reads it as the SOLE
authority: a finding on a changed line above `style` is a pre-confirmed `[lint]`
Critical (needs no verifier — the tool already ran); an uninstalled or crashed
checker is unreviewed scope that caps a would-be Approve; and — proof it ran — a
diff that carries an executable script but produced no readable report is itself
unreviewed (fail closed). A diff-only review, which has no worktree to run it,
is exempt. Nothing here comes from the input JSON a model wrote.
Removed the `script-lint` agent role (agent-briefs, agent-prompt weld, roster
requirement) and its SKILL.md agent entry; the roster's `hasExecutableScript` is now
exported as the shared predicate the gate reads. The `script-lint` command itself is
unchanged — it was always the deterministic engine; only its trigger and consumer moved.
* fix(review): address the second review pass on script-lint
- parseFindings failed OPEN on non-empty unparseable output (a version skew or a
deprecation line before the JSON) → `[]` → recorded as a clean `checked` file.
Return null on that path and push it to `errored`, fail-closed like a bad exit.
- hadolint had no config isolation while shellcheck gets `--norc`; a PR-controlled
`.hadolint.yaml` could `ignored:` its findings away. Add `--no-config`.
- pathTool recognised only `.sh`/`.bash`, but the shebang regex matches four shells
— a `.ksh`/`.dash` file never required the lint. Add those extensions so the
gate's owed-predicate and the command cannot disagree.
- Pin `r.ok` in the not-installed test, and add a mock test for the unparseable path.
* fix(review): address the deterministic-gate re-review — real correctness bugs on the write path
The second review pass on the gate found real bugs, including one this PR
introduced. Fixed:
- hadolint has no `--no-config` flag — a prior round added it, so hadolint exited
2 (usage error) on EVERY Dockerfile and reported it `errored`. Reverted to the
plain working invocation; hadolint config isolation needs a verified mechanism
and is tracked separately.
- actionlint's JSON anchors each diagnostic at the `run:` key line (not the
changed shell line) and flattens ShellCheck severity, so a style nit read as a
blocking `error` and a real finding read as pre-existing. Until that source
mapping is parsed and verified, a workflow is deferred to `skipped` (unreviewed)
and actionlint is never run — shellcheck still covers standalone shell.
- A recognised path that `firstLineOf` refused (a `hook.sh` symlink, a fifo) was
silently dropped from the report, so an empty report read as clean. It is now
recorded as `skipped`. `firstLineOf` distinguishes a true deletion (skip) from
an irregular file (record).
- The gate's owed-predicate excluded any file with zero added lines, so a
deletion-only edit that breaks a surviving `.sh` was treated as not-owed. Keyed
on the post-image (`fileLines`) now: a surviving script is owed, only a true
deletion is exempt.
- compose-review trusted any body-Critical string containing `[lint]` as
deterministic — a model-written or injected claim could launder itself past
verification. Provenance decides now: only `scriptLintGate`'s own findings are
deterministic (tracked by count); `[lint]` is no longer a trusted tag.
- A stale report from an earlier review of an older commit could certify the
current one if the lint step were skipped. The report records its `headSha`
(`git rev-parse HEAD`); compose-review rejects it as stale when it disagrees
with the plan's `fetchedSha`.
* fix(review): third gate re-review — read the report before the owed-predicate, fail closed on all paths
- The gate returned early on `hasExecutableScript` (path-only), so a shebang
script the command DID lint (`.husky/pre-commit`, detected by `#!`) had its
findings dropped — the gate never read the report. Read the report first; the
path-predicate now only gates the no-report fail-closed case. Findings from any
script the report names are processed.
- The staleness guard was a no-op when `report.headSha` was absent (git
unreadable): `planSha && report.headSha && …` short-circuited, so an
unverifiable report certified new code. It now fails closed on a missing headSha
when the plan names a commit.
- The plan-parse catch failed OPEN (returned "nothing owed") while every other
path fails closed. It now discloses "could not read the plan" as unreviewed.
- hadolint had no config isolation. Point `HADOLINT_CONFIG` at an empty file so a
PR-added `.hadolint.yaml` cannot suppress findings — an env var, benign if the
tool ignores it (unlike the invalid `--no-config` flag from the last round).
- `buildNote` hardcoded "not installed" for every skipped file; `skipped` now
mixes reasons (missing tool, irregular file, deferred checker), so it summarises
by tool and leaves the specific reason on each entry.
* fix(review): R3 — deferred (non-capping) actionlint, local-flow staleness, disclosure
- actionlint deferral capped EVERY workflow-touching PR (≈15% of merges): a
deferred workflow went to `skipped` → unreviewed → no Approve, on a checker
present but deliberately not run, which "install the tool" cannot fix. Split a
third `deferred` state — disclosed in the note, but the gate never reads it, so
it does not cap. `skipped`/`errored` still cap.
- The staleness guard was PR-only: `fetchedSha` is written by fetch-pr, not
capture-local, so a local review short-circuited the check and a stale local
report could certify new code — the exact fail-open, in the local flow.
capture-local now records the local HEAD as `fetchedSha`.
- The local path was armed (reviewMode `local`) but SKILL's `--worktree` was
unfillable there. SKILL now covers a local review (worktree = the project root)
and the derived report name.
- Nits: the skipped/irregular reasons no longer lead with the path (the gate
prefixes it — was printing it twice); merged the two `./lib/roster.js` imports.
- Tests: deferred does not cap; the staleness guard protects a local review too.
* fix(review): R4 — disclose deferred lint without capping; bind report freshness to diff content
B1 — the deferred checker was silent. R3 split a third `deferred` state so a
workflow's embedded shell (actionlint, whose source-mapping this env can't
verify) no longer caps the verdict — but the disclosure half was never wired:
`scriptLintGate` returned only `{criticals, unreviewed}`, so a workflow-only PR
went quiet on a file no checker examined. That is #10's original complaint
("only disclosed in prose") returning as disclosed nowhere. Add a third channel:
the gate now returns `disclosed`, populated from `report.deferred`, and
`composeReview` renders it in the body on every verdict — including Approve —
without pushing it into `cappedBy`. SKILL.md's contract paragraph now documents
the deferred state alongside unreviewed scope.
B2 — the staleness guard keyed on HEAD. A local review is defined by uncommitted
work, so `git rev-parse HEAD` is not a content identity for what it reviews: two
reviews at the same HEAD with different working-tree content shared a `fetchedSha`
and a stale clean report could certify broken code. Bind freshness to the diff's
content instead: `runScriptLint` stamps the report with a sha256 of the captured
diff (`diffHashOf`), and the gate re-hashes the plan's current diff and rejects a
mismatch. Correct for a PR (a later commit → a different diff) and for local
uncommitted work alike. `capture-local`'s `fetchedSha` plumbing is removed.
Tests: the DEFERRED-only case now asserts the disclosure is present and
non-capping (it previously pinned the silence). New composeReview gate tests kill
the surviving mutants — the gate critical stands with no verifier (provenance,
not the `[lint]` marker), and an errored checker caps a would-be Approve to
Comment. New runScriptLint tests pin the diff-hash stamp. 972 review tests,
ESLint --max-warnings 0, tsc, Prettier clean.
* fix(review): R5 — freshness fails closed when neither side has a hash; pin config isolation
The staleness guard was `report.diffHash !== planDiffHash`. When the plan names no
readable diff AND the report carries no hash, both are `undefined` and
`undefined !== undefined` is false — so the guard did not fire and an arbitrary
hashless report was accepted as this review's, its findings promoted to `[lint]`
Criticals. Every other branch of this gate fails closed; this one inverted, on the
exact unverifiable case its own comment claimed to reject. Fixed with `!planDiffHash
|| …`, and the stale `headShaOf` JSDoc left stacked above `diffHashOf` is removed.
The gate's happy-path tests were green *because* of that hole: `writePlan`/
`writeReport` (and the composeReview `gateReadyPlan`/`writeGateReport`) wrote no
diff and no hash, so every test that didn't override them ran through the fail-open
branch — proving the gate works on an *unverifiable* report, not a fresh one. The
fixtures are now FRESH by default: a captured diff exists (`DIFF` is a real file;
coverage only string-matches it, so this is transparent to it) and both plan and
report bind to its hash. A freshness test overrides one side to model staleness, and
a new test pins the both-undefined case directly.
Also closes the last mutation gap R5 flagged three rounds running: the config
isolation (`--norc`, `SHELLCHECK_OPTS` scrub, `HADOLINT_CONFIG` → empty file) is a
security property — a PR-added linter config must not suppress the finding the gate
blocks on — but it lived inside `runTool`, behind the spawn an injected runner
bypasses. Extracted `buildToolInvocation` (pure argv + env) so all three defences
are asserted without a binary; each was hand-mutated to confirm its test fails when
the defence is deleted.
976 review tests, ESLint --max-warnings 0, tsc, Prettier clean.
* test(review): pin the plan-parse disclosure — the last surviving gate mutant
`scriptLintGate` on an unreadable plan fails closed and pushes its own reason into
`unreviewed`. The coverage machinery already caps an unreadable plan, so the verdict
and its cap are identical with or without this line — what it loses when deleted is
the specific "could not read the plan" sentence in the body. That made it a
disclosure guarantee with no test, and the one gate mutant left standing after five
rounds. Pin it directly on the gate. 977 review tests, ESLint/tsc/Prettier clean.
* fix(review): harden script-lint tmp-file + spawn, leak-proof the tests (R8 suggestions)
Four suggestions from the re-review, all on script-lint:
- Symlink race on the hadolint empty-config: it was written to a fixed
`tmpdir()/qwen-review-hadolint-empty.yaml`, so on a shared runner a pre-planted
symlink there would have `writeFileSync` follow it and truncate the target. Write
it inside a fresh `mkdtempSync` directory instead — a 0700 dir with a random
suffix that cannot pre-exist, so the write is safe and the path unpredictable.
- `runTool`'s `spawnSync` had no `timeout`, unlike the sibling runners in
build-test.ts and test-efficacy.ts: a crafted script that hangs a linter would
block the review until the outer CI job timeout reclaimed the runner. Added a
120s bound; a timeout kills with SIGTERM, which the existing `r.signal` branch
already turns into a fail-closed error.
- Two test-cleanup leaks: script-lint.test.ts's symlink test made a second temp
dir cleaned only by an inline `rmSync` a failing assertion would skip, and
script-lint.mock.test.ts used inline `fresh()`/`clean()` with no hook. Both now
tear down in `afterEach`, which runs even when a test throws.
977 review tests, ESLint --max-warnings 0, tsc, Prettier clean.
* fix(review): hadolint isolation fails closed, not to a plantable path (R8)
R8 caught that the previous commit's fallback reopened the vector it closed. When
`mkdtempSync` failed, `emptyHadolintConfig` returned a FIXED
`tmpdir()/qwen-review-hadolint-none/x`. That path is a config hadolint *reads*, so an
attacker who plants an `ignored:` file there gets those ignores honoured — the
opposite threat direction from the write-truncation the mkdtemp move fixed. There is
now no predictable fallback: on failure `emptyHadolintConfig` returns `undefined`, the
env var is set only for a hadolint run and only when a private config exists, and
`runTool` fails the hadolint run CLOSED (errored) rather than lint against a config it
cannot vouch for.
Also from R8:
- The timeout comment claimed the `r.signal` branch handles a timeout. On Node a
timeout sets BOTH `r.error` (ETIMEDOUT) and `r.signal` (SIGTERM), and `r.error` is
checked first — so it is reported through the error branch. Comment corrected; still
fail-closed either way.
- Both hardening changes were untested. `buildToolInvocation` now returns `timeoutMs`
so the bound is one asserted value, and two tests pin it: HADOLINT_CONFIG is a fresh
0700 mkdtemp path (not the old fixed name), and the timeout is 120s. Each fails under
the corresponding mutation.
- The mkdtemp dir is swept at process exit, so the fixed-file-to-per-run-dir change
does not leak into the OS tmpdir.
979 review tests, ESLint --max-warnings 0, tsc, Prettier clean.
* test(review): pin the hadolint fail-closed guard in its own file (R9)
The `if (tool === 'hadolint' && !emptyHadolintConfig())` guard is what the previous
commit exists to add, yet deleting it left the suite green: `emptyHadolintConfig`
caches at module scope, so by the time any test in script-lint.mock.test.ts runs the
cache is warm and the failure path is unreachable from that file. A dedicated file
gets a fresh module registry — it points TMPDIR at a path that does not exist before
importing, so the first `emptyHadolintConfig()` call fails to mkdtemp and the guard
fires. Two tests, split so a mutation attributes cleanly: (1) buildToolInvocation
sets no HADOLINT_CONFIG (the env logic, holds either way); (2) runScriptLint errors a
Dockerfile closed rather than lint it unisolated (the guard). Deleting the guard
reddens (2) and leaves (1) green.
Also from R9: register the process-exit cleanup right after mkdtempSync, before the
write, so a writeFileSync that throws still leaves the temp dir swept, not leaked.
981 review tests, ESLint --max-warnings 0, tsc, Prettier clean.
* fix(review): isolate hadolint via --config, not the ignored HADOLINT_CONFIG env (R10)
Verified against real hadolint 2.14.0: the binary does not read `HADOLINT_CONFIG`
(its `strings` list it among no `HADOLINT_*` config vars; `-V` reports "No
configuration was specified"). It reads `--config`, then a `.hadolint.yaml` in the
process CWD, then XDG. Because a local review runs with `--worktree .`, the linter
ran inside the reviewed tree and honoured the diff's OWN `.hadolint.yaml` — so the
env-based "isolation" was a silent no-op and a PR could suppress its own Dockerfile
findings (a DL3018 `ignored:` made the finding vanish with nothing disclosed).
Isolate on the channel hadolint actually reads: pass `--config <private neutral
file>`, which overrides both the cwd and XDG configs. The config content is now
`ignored: []` rather than empty — `--config` rejects an empty file ("empty YAML
stream"). The mkdtemp 0700 dir and the fail-closed guard are kept (the config is a
path hadolint reads, so both still matter); the no-op `HADOLINT_CONFIG` /
`HADOLINT_NO_COLOR` env vars are dropped.
Tests updated to assert the real channel: hadolint's argv carries `--config` at a
fresh 0700 mkdtemp path holding `ignored: []`, and the isolation-unavailable path
adds no `--config` (and still fails closed). shellcheck's `--norc` /
`SHELLCHECK_OPTS` isolation is unchanged — it was verified sound against the real
binary. 981 review tests, ESLint/tsc/Prettier clean.
* fix(review): address maintainer review — worktree containment, hunk-fallback false positive, zsh/shebang docs
From @yiliang114's review of the script-lint gate:
- P1 shebang/zsh: `toolFor` intentionally omits zsh (and fish) — shellcheck refuses
both (`SC1071: ShellCheck only supports sh/bash/dash/ksh`), so routing a
`#!/usr/bin/env zsh` hook to it would make every zsh file a bogus SC1071 `[lint]`
Critical on its shebang. Documented the deliberate exclusion rather than adding zsh.
- P1 hunk fallback: `inDiff` fell back to the plan's context-inclusive hunks whenever
`addedRanges.get(path)` missed — including when the diff parsed fine but a path was
unmatched, which could promote a pre-existing finding on a context line to a
blocker. Now a parsed diff that does not mention a path yields `[]` (nothing added
→ nothing blocks); the context-inclusive `hunksOf` fallback is used only when NO
diff parsed at all (a report the freshness guard already rejects as stale), and that
degraded path now logs a warning.
- P2 worktree containment: `resolve(join(worktree, path))` now refuses a path that
escapes the worktree (`../../etc/passwd`), disclosing it as skipped rather than
stat-ing/linting a file outside the reviewed tree.
- P2 roster/gate shebang gap: made explicit in the gate's no-report branch that a
shebang-only script (which `hasExecutableScript` cannot see) is covered by the
always-run contract, not the `owed` predicate.
Two items left as tracked follow-ups per the reviewer's own framing (both flagged
low-priority / design): an aggregate cross-file time budget (the per-file 120s bound
is the main defense), and per-severity finding classification (the v1 all-or-nothing
stance is documented and defensible). 983 review tests, ESLint/tsc/Prettier clean.
* fix(review): harden the script-lint gate against the adversarial review round
From the Codex GPT-5 /review pass. Five fixed:
- Provenance by IDENTITY, not count (compose-review): the gate's `[lint]` criticals
are now tracked as a separate list rather than pushed into `bodyCriticals` and
removed by a count subtraction. The old `(filtered) − gateCount` misfired when a
model claim carried a `[build]/[test]/[probe]` tag (filtered out before the
subtract) or a gate finding's own text contained one — erasing an unrelated
claim's verification requirement. Model criticals alone decide the verify count.
- Distinguish unknown size from deletion (roster): `fileLines: 0` is a real
post-image count only in pr-worktree; in local/diff-only the report builder writes
0 for EVERY file, so keying deletion on it read a surviving `deploy.sh` as deleted
and let a missing report pass uncapped. `hasExecutableScript` now trusts
`fileLines` only in pr-worktree and owes any path-detected script otherwise.
- Hash the bytes used for range mapping (script-lint): the diff was read twice —
ranges before the linters, hash after — a TOCTOU window where a concurrent
recapture could pair snapshot A's ranges with snapshot B's hash. Read the diff
once into one buffer and derive both from it; an unparseable diff now yields no
`diffHash` (gate fails closed) instead of context-inclusive hunks.
- Escape PR-controlled paths in the body (compose-review): a diff filename can carry
a newline, `@mention`, HTML or Markdown; the gate's criticals/unreviewed/disclosed
strings rendered it verbatim into the posted body. Paths and linter messages now
render in an inline code span with backticks and newlines stripped (`mdField`).
- Classify lstat failures (script-lint): only ENOENT/ENOTDIR is "missing" (deleted);
EACCES/EIO/ELOOP is now `irregular` (skipped, fail closed) rather than silently
read as a deletion into an `ok: true` "nothing changed".
Three left as reasoned follow-ups: verifier-provenance for `[build]/[test]/[probe]`
tags (a pre-existing verification-model concern, not this gate); actionlint's native
workflow diagnostics (deferring the whole tool loses them, but separating native
from embedded-shell output needs an actionlint-output parser this env can't verify);
and cryptographic authentication of the report file (the orchestrator runs the
deterministic command and overwrites any pre-planted file; full anti-forgery is a
broader harness-trust change). 985 review tests, ESLint/tsc/Prettier clean.
* fix(review): clean error from the script-lint handler on a bad plan
Wrap `runScriptLint` in the command handler in try/catch, matching sibling
build-test: a missing or invalid plan makes it throw, and yargs' default handler
would print a stack trace the orchestrator has to parse. Emit the one-line message
and a non-zero exit instead; the gate still fails closed on the absent report.
* fix(review): scrub HADOLINT_* env, canonicalize worktree paths, portable isolation tests
Follow-up adversarial round on the previous fixes:
- Scrub HADOLINT_* from the child env (script-lint): hadolint 2.14 MERGES config from
HADOLINT_IGNORE / HADOLINT_OVERRIDE_* / HADOLINT_CONFIG with the explicit --config,
so an inherited one could suppress the findings the neutral config forces. Drop
every HADOLINT_* var, mirroring the SHELLCHECK_OPTS scrub — configuration comes
from our --config alone. Hostile-env regression test added (this also makes the
HADOLINT_CONFIG assertion hermetic under an inherited value).
- Canonicalize the worktree-containment check (script-lint): the lexical startsWith
guard is defeated by a symlinked ANCESTOR — a directory replaced with a symlink
leaves a child path lexically inside but resolving outside, and lstat/the linter
follow it. Add a realpathSync check against the canonical worktree; a path that
cannot be canonicalised is handled as missing downstream. Symlink-escape test added.
- Portable isolation tests: override TEMP/TMP as well as TMPDIR (os.tmpdir() ignores
TMPDIR on Windows, leaving the fail-closed path unexercised there), and guard the
0o700 mode-bit assertion with process.platform !== 'win32'.
Left tracked: fail-closed on a PARTIALLY parsed diff — narrow (capture writes the
diff in one writeFileSync, not incrementally) and already mitigated (a truncation
that later completes changes the hash, so the freshness guard rejects the report).
987 review tests, ESLint/tsc/Prettier clean.
---------
Co-authored-by: verify <verify@local>
|
||
|
|
1acc511809
|
feat(core): add Goal v3 worker tools (#7729)
* feat(core): add Goal v3 worker tools * fix(core): address Goal tool review blockers * fix(cli): complete Goal tool locale keys * test(core): cover Goal tool error propagation * fix(core): align Goal tool verification guidance * fix(core): harden Goal proposal validation * fix(core): align Goal blocker audit and verifier budget * fix(core): address Goal verification blockers * fix(core): keep Goal completion verifiable --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
d44030a4c0
|
feat(core): add model grade selection for subagent spawn (#7685) (#7702)
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
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
* docs: add design placeholder for subagent model grade selection (#7685) * feat(core): add subagent model grade selection * test(subagent): cover resolveModelGrade deep guards and resume else branch - subagent-manager: add tests for non-string grade values, blank values, array-shaped modelGrades, and missing modelGrades (all return undefined) - background-agent-resume: assert configured subagent model is preserved (not forced to 'inherit') when launch flags (model + authType) are absent Addresses test-coverage review findings. * refactor(subagent): extract normalizeModelGradeSettings and merge model validate - Extract normalizeModelGradeSettings helper shared by resolveModelGrade and the Agent tool schema build, so the advertised grades and runtime resolution cannot drift (addresses duplicated shape invariant). - Merge the three model-parameter validate branches under a single `params.model !== undefined` guard. - Update agent.test.ts mock to preserve the real helper while still mocking SubagentManager. * refactor(core): simplify model grade resolution * fix(core): reject unknown model grades * docs(core): clarify model grade precedence * docs: explain subagent model grades * test(core): update subagent manager mock * fix(core): list available model grades * fix(core): trim model grade keys and cover schema removal Grade keys were checked for emptiness via grade.trim() but stored in the map and advertised in the tool schema enum untrimmed, while values were trimmed. A padded key like ' small ' published a padded enum name the model had to reproduce verbatim, and the allowlist check silently excluded it. Normalize the key before storing, allowlist matching, and schema publication. Also adds a test for the delete schema.properties.model branch that fires when grades transition from available to empty, so a regression that breaks the delete leaves no stale model enum in the tool schema. * fix(core): trim allowed model grade filters --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
d1988fd83d
|
feat(review): give the verifier a probe capability — run a runnable claim, don't just read it (#7756)
Measured on this repo: read-only verification is where the review misses its hardest bugs. Handed the exact low-confidence finding for a `!` command that executes twice, the strongest model's Step-3 agents traced the mechanism and called it correct (0/3 across the full roster). Validated the fix directly — when the same models (3.7-max and 3.8-max-preview) were allowed to WRITE AND RUN a probe, both confirmed the double-execute from observed behaviour (`sendShellCommand called twice with ["git push"]`), both did the probe-validity self-check unprompted, and a plausible-but-false negative control was correctly refuted with no fabrication. So the verifier's brief now says: when a finding's failure scenario is a runnable claim about a named unit and the repo has a fast unit harness (vitest/jest/pytest), write a minimal probe, run it, and let the observed behaviour settle the verdict. Two rules keep it evidence rather than theatre — a mandatory self-check that the probe flips between buggy and correct, and leaving the tree exactly as found. A finding a probe confirmed carries `Source: [probe]`, which compose-review treats as deterministic (a run produced it) like `[build]`/`[test]`. This is Phase 1: the agent-driven loop the validation exercised, no new command. The deterministic runner + artifact and the finding-generation side (emitting a probeable low-confidence finding rather than concluding "correct") are follow-ups. Co-authored-by: verify <verify@local> |
||
|
|
60812d4cd3
|
fix(cli): show tool descriptions in multi-tool compact summaries (#7589)
* fix(cli): show tool descriptions in multi-tool compact summaries (#6014) buildToolSummary() previously discarded descriptions when 2+ tools of the same category were grouped, showing only counts like "Read 3 files" or "Searched 2 patterns". Now shows actual descriptions inline when ≤3 tools (e.g. "Read a.ts, b.ts, c.ts"), and first 2 + "...and N more" when >3 tools. getActiveToolHint() skips the redundant ⎿ hint line when descriptions are already visible inline. * fix(cli): use i18n key for '...and N more' phrase Use existing '... and {{count}} more' translation key instead of hardcoded English. This ensures the phrase is localized correctly for all 9 shipped locales (fr, zh, de, zh-TW, ca, ja, ru, pt, en). Also updates test expectations to match the i18n format (space before 'and' instead of no space). Addresses CR suggestion on PR #7589. |
||
|
|
9bdc62c74b
|
perf(cli): replace comment-json settings parser (#7747)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
7422bf8797
|
fix(cli): handle escaped dollars around inline math (#7741)
* fix(cli): handle escaped dollars around inline math * test(cli): add escaped-dollar TUI capture scenario --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
592b3d429d
|
fix(cli): complete repeated skill slash commands (#7720)
* fix(cli): complete repeated skill slash commands * fix(cli): scope stacked skill completion * fix(cli): suppress invalid stacked skill ghost text * refactor(cli): centralize stacked skill eligibility * fix(cli): align stacked skill highlighting * test(cli): cover stacked skill completion guards --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |