Commit graph

8045 commits

Author SHA1 Message Date
github-actions[bot]
456fc9b02d chore(release): v0.21.2 2026-07-31 03:06:31 +00:00
dreamWB
a182bdf618
fix(web-shell): constrain session details and add copy action (#8127)
Some checks are pending
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
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
* fix(web-shell): constrain session details and add copy action

* fix(web-shell): harden constrained session details

* fix(web-shell): close session details review gaps

* fix(web-shell): address session details follow-up
2026-07-31 02:25:44 +00:00
ytahdn
e379ea4e53
feat(web-shell): enhance composer and empty-session animations (#8098)
* feat(web-shell): enhance composer animations

* fix(web-shell): refresh DPR on resize and cover animation a11y guards

Re-read devicePixelRatio inside each resize so the specular composer
effect and the new-session dot field keep a correctly sized backing
store when the page zoom or display scale factor changes. Add
component-level coverage for the prefers-reduced-motion guards and for
the typewriter replay after the empty editor loses focus.

* fix(web-shell): localise specular highlight, fix rotation direction, add idle bail-out (#8098)

* fix(web-shell): correct specular angle wrap and dot field pointer baseline (#8098)

* fix(web-shell): paint static dot grid and idle animation loops (#8098)

* fix(web-shell): reset specular proximity on pointer leave and test WebGL cleanup (#8098)

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-31 02:20:27 +00:00
Shaojin Wen
702932cc7c
fix(test): make auto-edit permission E2E deterministic (#8154)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-31 00:01:18 +00:00
Shaojin Wen
584f6a4bec
fix(test): allow slow permission mode E2E responses (#8149)
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
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 16:32:10 +00:00
qqqys
b0c0cc3338
feat(web-shell): manage pairing approvals (#8081)
* feat(web-shell): manage pairing approvals

* test(web-shell): cover pairing approval failures

* fix(web-shell): avoid stale pairing approval errors

* test(web-shell): cover pairing revoke cancellation

* fix(web-shell): prevent pairing approval races

* fix(web-shell): address review feedback on pairing approvals (#8081)

* fix(web-shell): keep revoke confirm title fallback type-safe (#8081)

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 15:58:23 +00:00
Shaojin Wen
a1c33de18f
fix(web-shell): isolate worktree session execution (#8068)
* fix(web-shell): isolate worktree session execution

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

* test(mcp): expect session cwd in effective config

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

* test(acp-bridge): cover shell execution after a failed cwd change (#8068)

* fix(acp-bridge): abort direct shell waiting on a hung cwd change (#8068)

* refactor(core): centralize effective MCP server resolution (#8068)

Extract McpClientManager.getEffectiveMcpServers() as the single source of truth for the populateMcpServerCommand recipe, replacing six duplicated call sites that this PR otherwise had to edit in lockstep. Also cover relocateWorkingDirectory's combined memory + MCP refresh failure path so both errors stay surfaced.

* fix(core): align MCP cwd stamping with transport classifier (#8068)

* fix(core): move MCP cwd stamping to populateMcpServerCommand (#8068)

getMcpServers() injected cwd into the effective config, which changed
the hash used for MCP approval binding (hashMcpServerConfig). This
caused approved project/workspace-scoped stdio servers to show as
pending approval, and re-approving silently gated them off on the next
daemon start.

Move the cwd stamping down to populateMcpServerCommand — the
transport-config seam every spawn path already goes through — so
getMcpServers() stays an identity-stable view of configuration and the
approval hash contract holds. Also make populateMcpServerCommand
non-mutating (spread instead of in-place assignment).

Additionally, clean up the abort listener registered during the
direct-shell cwd-queue race so it does not accumulate on the
long-lived per-session signal (ACP path).

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 15:38:30 +00:00
Shaojin Wen
c3620bc7a0
fix(serve): restore build by routing side-task rollback through the workspace session service (#8144)
The side-task creation route still constructed a session service directly on
two rollback paths after the surrounding module moved to the workspace runtime
helper, leaving the identifier unbound and breaking the CLI build on main.

Route both rollbacks through the workspace runtime helper so they honour the
per-workspace runtime base dir like every other session-service use in this
module.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:54:42 +00:00
jinye
3bdaeac046
fix(serve): Isolate daemon session maintenance writers (#7975)
* fix(serve): isolate daemon session maintenance writers

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

* codex: fix CI failure on PR #7975

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

* codex: address PR review feedback (#7975)

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

* codex: fix CI failure on PR #7975

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

* fix(cli): address review feedback on daemon session writer maintenance (#7975)

- Extract teardownBoundSession helper to deduplicate bound-session
  teardown in scheduled-tasks create handler
- Extract shared cleanupSession callback in createServeApp to eliminate
  three verbatim copies of the orphan-deletion wrapper
- Fire onError callback on the SessionNotFoundError deletion path in
  deleteDaemonSessions, matching the normal close-succeeded path
- Update stale @priority docstring on Storage.getRuntimeBaseDir()

* codex: address PR review feedback (#7975)

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

* test(cli): cover orphan-delete paths and draining guards (#7975)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-30 14:01:50 +00:00
destire-mio
079ce5346a
feat(agent): add fork tool execution allowlist (#8066)
* feat(agent): add fork tool execution allowlist

* fix(agent): address fork allowlist review feedback

---------

Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 13:58:51 +00:00
ytahdn
0a3098a279
feat(web-shell): add contextual task panels (#7929)
* feat(web-shell): add contextual task panels

* fix(web-shell): harden contextual task panels

* fix(web-shell): preserve side task titles

* fix(web-shell): address review feedback on context panels PR (#7929)

- Add POST /session/:id/side-task to telemetry route catalog (51 routes)
- Increase SDK browser bundle size limit to 184KB
- Fix duplicated data-testid="chat-pane" → "chat-pane-container" on container
- Gate sourceType behind session_source_metadata capability check
- Add removeSession cleanup after killSession in !res.writable path
- Add i18n key sideTask.renameFailed for error fallback
- Add unit tests for selectVisibleHistoryRecords invariant

* fix(cli): update telemetry-catalog route drift guard to 51 routes (#7929)

* fix(web-shell): address review feedback round 2 on context panels PR (#7929)

- Fix /fork sider discarding createSideTask() return value: show toast
  when side tasks are unavailable
- Fix layout feedback loop: availableWidth no longer depends on
  environmentPanelVisible since the CSS overlay does not change the
  chat pane DOM width
- Remove dead environmentPanelSuppressed state (never set to true)
- Restore setArtifactPanelOpen(false) in closeArtifactPanelTab when
  the last tab is closed
- Extract agentDisplayName(task) to a local variable to avoid triple
  invocation per render

* fix(web-shell): dedupe completed background agents in environment panel (#7929)

getEnvironmentAgentTasks correlated a transcript tool card with the live
/tasks snapshot only on toolUseId, the notification taskId, and a
<subagentType>-<callId> derived id. A completed background agent can lose
that linkage (its live task carries no usable toolUseId and its daemon id
is general-purpose-<internalId>), so the trailing loop appended the live
task as a second entry. Add a conservative content fallback (prompt, or
description+subagentType) mirroring the daemon's legacy resolver.

* feat(web-shell): support side tasks during active turns

* fix(web-shell): deduplicate completed subagents and gate sourceType on capability (#7929)

* fix(web-shell): restore background agent reconciliation and fix agent dedupe (#7929)

Restore the one-shot subagent reconciliation for inline background Agent tool
cards. Persisted notification records do not always retain a toolUseId, so the
SSE discrete-notification path alone can leave a card stuck in Running; the
documented fallback resolves pending cards through the subagent endpoint after
catch-up, reconnect, and terminal notifications.

Also stop the loose description content fallback in getEnvironmentAgentTasks
from claiming a live task that another transcript tool call already links
precisely (by toolUseId, message taskId, or derived id). Two agents sharing a
description previously collapsed into one: the fallback stole the linked task,
its owner re-matched the same task, and the orphan was dropped.

* fix(web-shell): address critical review feedback on context panels (#7929)

* fix(web-shell): reconcile side-task state across sessions and listings (#7929)

* fix(web-shell): preserve contextual panel fallbacks

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-30 13:45:30 +00:00
Shaojin Wen
36a3fb2fa2
feat(review): statement-level mutation probes in test-efficacy (#8020)
* feat(review): statement-level mutation probes in test-efficacy

The revert probe is all-or-nothing: it reverts whole production files, so a
suite that gates six of a diff's behaviours goes red and the probe says
"gated" — even when the seventh behaviour, a one-line safety statement, has no
test at all. Dogfooded on a live PR: deleting a single `reminders.clear()`
inside the not-continued branch left the full 471-test suite green, and that
line carried the PR's headline safety property (an abandoned task's todos must
not bleed into an unrelated new prompt). A human reviewer found it with a
hand-rolled mutation probe; the command could not.

Add the probe kind the human ran, deterministically. Candidates are added
lines from the committed head (never the dirty worktree) whose trimmed
statement calls a safety verb — .clear(), .delete(), .reset(), .abort(),
.removeListener(), .unref() — or reassigns state to empty ([] / new
Map()/Set()), and that are removable as a whole: single complete expression
statements, brace-balanced via a string/comment-aware scanner, previous
significant line ending ;, { or } (which rejects fluent tails, continuations,
and the brace-less-if silent-rebind trap), outside template literals and block
comments. Selection is conservative by design: a false negative costs
nothing, a false positive burns a suite run.

Each mutant (capped at 8, files with collocated new tests first) deletes its
one line in the existing probe worktree and re-runs the affected suites
through the existing vitest-json classifier: red = killed (the line is
guarded), green = SURVIVED — the invariant it enforces ships unprotected —
filed as a finding in the unreachable/inert register so the Agent-7 pipeline
picks it up without any skill change. Compile/load failure = inconclusive,
never a finding. Mutants run only after a cleanly green baseline, inside the
command's existing deadline budget (each run must leave room for the revert
probe); candidates that no longer fit are counted, not silently dropped.

* fix(cli): harden test-efficacy mutant selection text checks (#8020)

Mutant selection ran its end-anchored checks on raw trimmed lines, so a
trailing comment hid a statement's real end (dropping genuine candidates
like `reminders.clear(); // why`) and a safety verb inside a string faked
one (a wasted suite run plus a misleading survivor). Route the
`SAFETY_VERB_RE`, `endsWith(';')`, and predecessor `/[;{}]$/` checks
through a shared `codeOnly()` that strips comments and blanks literal
contents first.

Also guard the template-state escape skip against a backslash-continued
line, mirroring the single/double-quote branch: swallowing that newline
dropped a per-line literal flag and shifted every later line's verdict.

Adds unit coverage for all three selection fixes and an integration test
for the baseline-not-green skip branch.

* fix(cli): gitignore fake vitest in test-efficacy integration fixture (#8020)

The fake vitest bin was committed by `git add -A` and checked out into
the probe worktree as the stale passing copy, so installFailingVitest's
overwrite in the main worktree was never seen by npx in the probe tree.
The baseline read green and the baseline-not-green skip test failed.

* fix(review): make mutation-probe reporting precise (#8020)

Address review feedback on the test-efficacy mutation probe:

- Count candidates the MAX_MUTANTS cap drops in `skippedForCap` instead of
  silently losing them, so a capped `survived: 0` cannot read as "every
  safety statement is covered" (mirrors the existing `skippedForBudget`).
- Gate the mutant phase per probe file: run each mutant against the files
  that are green in the unmutated baseline, so one unrelated quarantined
  (all-skip) suite — `inconclusive`, not red — no longer disables the whole
  probe.
- Scope the `mutant-survived` finding to the diff's own tests ("confirm an
  existing test covers it, or add one") rather than asserting the invariant
  ships unprotected, which an untouched pre-existing test may still cover.

* test(review): cover the budget-skip path in mutation-probe integration tests (#8020)

* fix(review): harden mutation-probe selection and diff parsing (#8020)

* fix(review): handle multi-line class headers in mutation-probe selection (#8020)

* fix(review): stop class-body walk at braces before matching class keyword (#8020)

* fix(review): whole-file literal scan for mutant selection; pin the untested guard paths

The per-line scanner pair (codeOnly + lineStartsInsideLiteral) shared a blind
spot: a backtick inside a `${…}` interpolation read as the outer template's
closing backtick. That flipped the literal state for every following line and,
in the single-line skip, exposed nested-template content as code — so a safety
verb inside a string could be selected as a mutant (a false-finding vector) and
a class field below a brace-bearing template could slip the class-body
rejection. Replace the pair with one whole-file pass that tracks interpolation
brace depth: per-line code text with comments stripped and literal contents
blanked, plus the same `${}`-aware skip for delimiter scanning. Differential
audit over every core+cli source file: zero selection differences on real code;
the pathological shapes are pinned by three new tests.

Also pin the remaining untested paths from review: the selection-failure catch
(discloses and still runs the revert probe), the runOneMutant line-mismatch
guard (now exported; inconclusive, file untouched), and the budget-skip stdout
disclosure.

* fix(review): clamp probe deadlines to the whole-command budget (#8020)

* fix(review): harden mutant selection guards and disclosure accounting (#8020)

* fix(review): close silent-zero paths in mutant disclosure and harden diff parsing (#8020)

* fix(review): drop interpolation quote-skip that mis-parsed regex literals (#8020)

* fix(review): track template nesting with a stack; disclose derailed files; harden and pin the remaining probe paths

The nested-template fix that landed as a counter cannot represent a nested
template INSIDE a nested interpolation: at two levels the deep template's text
`}` is charged against the wrong frame, the scan desyncs, and the file either
admits template text as a mutant or derail-drops its real candidates. Replace
the counter with a stack — one frame per open template, `}` closes only the
top interpolation, a backtick closes only the innermost template — and derive
the end state from the stack. The two-level trigger is pinned by a test
written red-first against the counter.

Derailed files are now disclosed, not silently dropped: selectMutants returns
them, and the note composer stacks the derail note with the red-baseline note
instead of clobbering. The hostile-git-config path gets its missing test (repo
diff.srcPrefix/dstPrefix, diff.external, core.quotePath with a non-ASCII
path — fails with the pinned flags removed). The budget test drops its
Date.now call-count coupling for an injected clock threaded through
runTestEfficacy/runProbeSuite. The mutation-phase catch gets an end-to-end
test (ENOBUFS mid-phase → all candidates inconclusive, revert probe still
runs, report still written).

---------

Co-authored-by: verify <verify@local>
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 Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-30 13:06:35 +00:00
jinye
c50120985b
fix(serve): Prevent repeated workspace skill rescans (#8080)
* fix(serve): prevent repeated workspace skill rescans

Make workspace skill status reads use committed snapshots and move refresh work to explicit mutation paths. Add generation-safe daemon caching, conditional HTTP responses, SDK revalidation, and multi-session extension refresh safeguards.

Refs #8079

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

* fix(serve): narrow the workspace-skills read model and close its regressions

Follow-up to the previous commit on this branch, from reviewing it.

Subtractions — these were separable from the fix and carried more surface
than value, so they move out of this change:

- Revert the ETag / If-None-Match layer (CORS allow+expose headers, the SDK
  conditional JSON cache, the browser bundle budget bump). Express already
  emits an ETag and answers 304 for these routes, so the only new behavior
  was the SDK cache. It saves transfer bytes but no daemon work — the ETag is
  a hash of the already-serialized body — and it shipped without a paired
  `Cache-Control`, which is what actually keeps an intermediary from serving
  a stale snapshot of an authenticated, mutable resource. The SDK cache was
  also unbounded, with no eviction or clear entry point.
- Revert moving `extensions_final` ahead of skill initialization in
  `Config.initialize`. In non-safe, non-bare mode `extensions_initial` is
  already the same argument-less `refreshCache()`, and it runs
  `applyStoreActivation`, so `getActiveExtensions()` is fully populated
  before skills are enumerated either way. The move changed only startup
  event order (and pushed permissionManager past the extension refresh) for
  every surface including the interactive CLI.

Regression fixes — the read went pure, but two of its inputs lost their only
path back to disk:

- Extension sources have no watcher, unlike skills. With the per-read
  `extensionManager.refreshCache()` gone, an extension installed, removed,
  enabled, or disabled outside the daemon would never reach the snapshot
  until the child restarted — and because extension-level skills are derived
  from the extension set, a skill-watcher tick could not recover it either.
  Adds `ExtensionManager.refreshCacheIfSourcesChanged()`: a stat-based
  fingerprint over the extension directory entries, each manifest, the
  enablement file, and the store state, which refreshes only when they moved.
  A status read pays one readdir plus one stat per entry instead of a
  directory scan and a full parse, and stays self-healing.

  The baseline is the pre-load fingerprint, so a change landing during a
  refresh stays visible to the next check instead of being masked by a
  post-load stat. The directory and store halves are captured at different
  points because a refresh writes the store itself but never the manifests.

- Revalidation is skipped in safe and bare mode, and the whole of it —
  including that mode check — sits inside its error boundary. Those modes never
  populate the extension cache by design, while the snapshot derives extension
  skills from `getExtensions()`, so revalidating there would have loaded the
  extensions the mode exists to exclude. Keeping the mode check outside the
  boundary would also have let a config missing those accessors fail a read.

- `initialized: true` with an empty list when the config has no
  `SkillManager` is now `initialized: false`. The daemon latches any
  initialized answer into `lastWorkspaceSkillsStatus` and then prefers it
  over its own local enumeration, so the old value could suppress the
  fallback permanently.

Also:

- The retained-snapshot path bumped the freshness timestamp without checking
  its generation, so a read that started before an invalidation could push
  out the TTL of a snapshot a later read had committed — letting a
  post-mutation snapshot go unrevalidated for longer than the window.
- `setWorkspaceSkillEnabled` folded `configsFailed` into `sessionsFailed`,
  but it sends `reason: 'settings'`, which never refreshes a skill cache, so
  the term was structurally zero. Report `configsFailed` from the `content`
  path instead, where it can actually be non-zero.
- Documents the settings-freshness gap this read model accepts: enablement
  now comes from the child's in-memory `LoadedSettings`, which `SettingsWatcher`
  keeps current for the User and Workspace scopes but not for System /
  SystemDefaults (locked-skill policy) or an untrusted workspace.

Tests: adds a real-filesystem guard that drives 50 consecutive cached reads
and asserts zero additional readdir/readFile calls — the mocked suites could
only prove `refreshCache` was not *called*, which is not the invariant that
broke. Adds coverage for the fingerprint gate (steady state, install,
removal, in-place manifest edit, concurrent callers, and the mid-refresh
race), for the null-manager, moved-sources, and safe/bare-mode read paths, and
for the generation guard. The generation-guard and safe/bare-mode tests were
each verified to fail with their fix reverted.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 12:52:37 +00:00
qwen-code-dev-bot
b2c77d224c
fix(test): give multi-model E2E turns more CI timeout headroom (#8108) (#8111)
Co-authored-by: Qwen Code Autofix <qwen-autofix@users.noreply.github.com>
2026-07-30 12:47:10 +00:00
jinye
f3ad4fcffb
feat(serve): page large text files by byte cursor (#8002)
* 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>

* refactor(core): thread the descriptor instead of forking text-read helpers

PR #7947 pinned large-text reads to one inode by threading a caller-owned
FileHandle into readTextRange as an optional field, plus a second field,
forceStreaming, to suppress the buffering fast path. Two optional fields
produced four combinations: one meaningful, one used by a single test, one
unreachable, and — in readFileWithLineAndLimit — one that silently fell
through to a by-path read, defeating the reason the caller opened a handle.

Unify the two encoding detectors. detectFileEncoding now takes a path or a
borrowed handle, so detectFileHandleEncoding is deleted along with the
message discrepancy between them: an encoding iconv-lite cannot load now
raises LargeNonUtf8TextError naming that encoding rather than deferring to
the decoder's generic invalid-utf8 variant. Both still refuse the file, and
the Serve boundary maps both to binary_file.

Split the reader into readTextRange (path) and readTextRangeFromHandle
(always streams, both byte bounds required). The unreachable combination and
its untested readFileHandleBuffer are gone, and with no fileHandle parameter
left for readFileWithLineAndLimit to ignore, the RangeError guarding that
fallthrough is deleted too — the trap can no longer be expressed.

CoreReadTextFileHandleRequest drops its required stats field. Nothing
downstream read it, and because the ACP request type it extends permits
extra properties, TypeScript accepted the dead argument silently.

readFileHandleChunks becomes chunksFromHandle(fh, from) — the one seam
byte-cursor text paging needs.

No observable change at the Serve boundary: its 222 tests pass unmodified.
Two fileSystemService tests were deleted rather than repaired; they asserted
the arguments readFileWithLineAndLimit received, which is nothing once the
handle path stops calling it. Their coverage lives in read-text-range.test.ts
against real files and in workspace-file-system.test.ts at the real boundary.

258 production lines in core, net -71 overall.

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

* refactor(core): make CoreReadTextFileHandleRequest standalone

Self-audit follow-up to f55c867a. Two fields survived the reshape that the
handle path never reads:

- `stats` was documented as required ("must pass the Stats captured from that
  handle") and nothing downstream read it. The handle path always streams, so
  it never needs a size to choose a strategy, and the encoding probe does its
  own fstat.
- `path` became dead once readTextRangeFromHandle replaced the path-plus-handle
  call. Errors are labelled with the path by the Serve boundary that owns it.

Neither was caught by the compiler: the ACP ReadTextFileRequest the type
derived from permits extra properties, so the CLI kept passing both silently.
That is the argument for declaring the type standalone rather than Omit-ing
four of six inherited fields and quietly re-admitting the rest.

Also record the second behaviour delta of the detector merge in the design
doc: detectFileEncoding catches I/O errors and falls back to 'utf-8', where
detectFileHandleEncoding let them propagate. The failure is not lost — a handle
that fails the 8 KiB probe fails the streaming read immediately after — but a
different call now reports it.

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

* feat(serve): page large text files by byte cursor

Line offsets address a byte stream, so `readText` resolves them by scanning
from byte 0. Paging a large log that way is O(n^2) across pages, and past
MAX_TEXT_SCAN_BYTES (8 MiB) a deep page is refused outright — agents had no
O(1) path short of dropping to GET /file/bytes and splitting lines themselves,
losing encoding handling, multibyte safety, and the binary_file refusal.

A response that leaves content behind now returns `hasMore`, and where a file
byte offset is derivable, an opaque `nextCursor`. Passing it back as `cursor`
resumes in O(1). Page 1 is an ordinary `limit` read, so clients never compute
byte offsets themselves, and a paging loop does not break when a file happens
to be small.

The cursor is unsigned base64url JSON carrying {off, size, dev, ino}, matching
encodeOrganizedCursor rather than the HMAC-signed transcript codec: the path is
re-resolved through the workspace boundary on every request, so a forged cursor
can only move the offset within a file the caller may already read — what
GET /file/bytes?offset= allows today. What the payload is for is staleness:
a replaced or truncated file yields hash_mismatch instead of bytes from the
wrong place, while an append leaves an outstanding cursor valid — the case the
feature exists for.

Every minted cursor points at the start of a line. When a single line exceeds
maxOutputBytes the reader emits a truncated prefix and skips to the next line
rather than resuming mid-line, because a mid-line cursor makes the following
page snap forward and silently drop the rest of that line at the seam. Windows
cut mid-line by a byte cap therefore report hasMore with no cursor, as do
non-UTF-8 snapshot reads whose decoded text is a UTF-8 re-encoding with no
mapping back to file offsets. That is why hasMore is a field rather than a
restatement of nextCursor.

Cursor reads branch before the size check, not by widening the window gate:
a cursor read of a file under MAX_READ_BYTES would otherwise land on the
snapshot path, which knows only line/limit, and silently return line 0.

Adds the workspace_file_read_cursor capability, per the convention that new
behavior gets a new tag, and retargets the scan-budget hint at cursor paging.

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

* fix(core): advance UTF-8 cursors after truncation

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

* docs(serve): clarify cursor bootstrap limits

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

* fix(sdk): raise daemon browser bundle budget

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

* test(serve): cover ACP cursor dispatch and cursor binary_file mapping (#8002)

* fix(core): only set sawCrlf for emitted lines in cursor paging (#8002)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-30 12:07:05 +00:00
qwen-code-dev-bot
467ed9884d
fix(test): resolve turn completion on result messages in setModel E2E test (#8072) (#8075)
* fix(test): resolve turn completion on result messages in setModel E2E test (#8072)

* fix(test): apply result-only turn completion to permission-control tests (#8072)

* fix(test): add result-only turn completion comments to permission-control tests (#8072)

---------

Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-30 11:57:51 +00:00
qqqys
9eab8bb301
fix(core): abort workflows during session shutdown (#8107)
Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 11:50:20 +00:00
zjgzx1988
953c9d8177
feat(core): tag UserPromptSubmit hook context and record display provenance (#7956)
* feat(core): tag UserPromptSubmit hook context and record display provenance

UserPromptSubmit additionalContext was appended to the request as a bare
text part and persisted verbatim, so hook-injected text was
indistinguishable from user-authored text in the transcript, polluted
resumed sessions, telemetry, and auto-memory recall queries.

- Wrap injected context in a reserved
  <qwen:user-prompt-submit-context> tag (hook output already escapes
  angle brackets, so the tag cannot be forged from inside).
- Record the pre-injection user prompt as systemPayload.displayText plus
  the injected string as hookContext on the user record; the model-bound
  message stays verbatim for faithful resume replay.
- Use the pre-injection prompt text for telemetry prompt attributes and
  managed auto-memory recall.
- Resume projection prefers displayText, strips a trailing whole-part
  tagged block when no payload exists, and leaves legacy bare-injected
  records unchanged.
- Apply the same tag wrapping on the ACP session injection path, which
  already records the pre-injection prompt.

Closes #7940

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: note UPS promptText TDZ ordering and sole-part resume guard

Document the conflict-resolution constraint that promptText must be
declared before the injection assignment, and the sole-part read-path
guard that keeps a user-authored whole-tag message intact.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(cli): cover at_command resume with tagged UPS context

Confirm the at_command branch still prefers payload.userText when a
paired user record carries a trailing tagged hook-context part, and
falls back to the tag-stripping projection only when userText is absent.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): address PR 7956 review findings and Goal recording spy

Omit the optional UserPromptRecordPayload third arg when no hook
injected, so Goal admission spies expecting two args stay exact and
CI client-goal.test.ts passes.

Project plain UserPromptSubmit-augmented records through
transcript-replay with the same displayText / trailing-tag strip
fallback as the TUI, covering ACP/export surfaces. Strengthen the
displayText preference fixture so it disagrees with the tag-strip
path, and use the named UserPromptRecordPayload type in resume.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(acp-bridge): import UPS tag helper via Node-free package export

transcript-replay is inlined into the browser daemon/transcript SDK
bundle. Importing isUserPromptSubmitContextPartText from the core
package barrel pulled the whole Node-bound core graph into that
bundle and failed CI (esbuild Could not resolve "node:*") across
Test, web-shell E2E, and Real daemon E2E.

Export the pure helper as @qwen-code/qwen-code-core/userPromptSubmitContext
and import that path instead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(test): alias userPromptSubmitContext for Vitest source resolution

CLI and acp-bridge Vitest configs already map goalWire/transcriptRecords
to TypeScript sources; without the same alias the new package export
fails import analysis and breaks dozens of CLI suites.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(acp-bridge): keep images when projecting displayText user records

Preferring UserPromptSubmit displayText previously returned early and
skipped projectMessageParts, dropping multimodal inlineData. Rebuild
parts so displayText replaces text while images keep their order.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(core): drop unused hookContext and cover image-only displayText

UserPromptRecordPayload.hookContext had no read sites; keep displayText
only and recover injected text from the tagged message part. Also cover
the image-only !replaced append path and simplify the recording guard.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: cover remaining UserPromptSubmit provenance Suggestions

Share stripTrailingUserPromptSubmitContextPart between TUI resume and
ACP replay, assert ACP Session tags additionalContext, and lock
telemetry to the pre-injection prompt text.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
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>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 11:45:23 +00:00
Shaojin Wen
cc508dd7ba
fix(autofix): salvage race-lost pushes by merging the moved head and retrying (#8042)
* fix(autofix): salvage race-lost pushes by merging the moved head and retrying

The review-address push is one-shot: when anything pushes to the PR head
during the agent's ~50-minute window, the final push dies 'fetch first'
and the entire verified agent run is discarded. The per-PR head-write
concurrency group cannot prevent this — it serialises this repo's
workflows, not the PR author or the fork side. Observed twice in one day
(#7983 after a 56-minute run, #7985 after 43 minutes).

On rejection, fetch the moved head, merge it into the local line, and
retry (bounded at 3 attempts). Merge rather than rebase: the agent's own
conflict-resolution rounds create merge commits, and a rebase would
flatten them and can silently re-introduce the conflicts they resolved.
The merge result descends from the remote head, so the retried push is a
fast-forward and rewrites nothing. A genuine content conflict aborts the
merge and falls through to the existing failure path unchanged.

When a salvage merge happened, the round report discloses that the
round's verification predates the merge so mid-run commits get
re-checked by a human.

* fix(autofix): address salvage-loop review findings

- Gate the PUSH_RACE_MERGED disclosure on HEAD actually advancing: a
  transient push failure (upload timeout, 503) on an unmoved branch
  no-ops the merge ('Already up to date') and must not tell the
  reviewer to re-check mid-run commits that never existed.
- Annotate the salvage fetch failure with ::error:: like the two
  adjacent failure paths, so a deleted fork branch or network error
  does not kill the step with an unannotated exit 128 under bash -e.
- Re-pin the same-repo push URL construction in tests: it lost its old
  'origin "${BRANCH}"' pin in this rework, leaving a ${REPO}→${HEAD_REPO}
  mutation (malformed remote in the same-repo case) unkillable.

* test(autofix): restore dropped mutation-killing pins and add structural assertions (#8042)

* test(autofix): pin exit 1 in the give-up guard regex to kill the deletion mutation (#8042)

* test(autofix): pin exit 1 in the fetch-failure and merge-conflict salvage paths (#8042)

* test(autofix): strengthen salvage-test pins to kill init-value and capture-order mutations (#8042)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 11:38:47 +00:00
Shaojin Wen
f6ae9202b1
feat(autofix): per-source feedback budget in Critical-only mode (#8071)
* fix(autofix): never defer maintainer feedback in Critical-only mode

Critical-only mode (after 5 change-producing rounds) classifies
feedback lexically: only a literal **[Critical]** tag or a
CHANGES_REQUESTED review survives; everything else is deferred before
the agent reads a word of it. That rule was built to stop the review
bot's suggestion ping-pong, but it catches maintainers too. Observed
four times in two days (#8037, #7944, #7885, #7799): a maintainer's
review with explicit merge-blocking findings — #8037's said 'I'd fix
before merge' on a correctness bug and a security-adjacent one — was
wholesale-deferred as one 'non-Critical item', and the bot then
reported 'No Critical feedback. The Issue-level comments sections are
empty', which was lexically true and substantively false. The bot's
own advertised definition ('correctness bugs, security issues, or
formally requested changes') is exactly what the deferred comments
contained; the agent that could have applied that definition never saw
them.

The lexical test now applies exclusively to the review bot's output:

- All three actionable filters (reviews, inline, issue-level) pass
  anything not authored by the review bot straight through in
  Critical-only mode — the agent judges maintainer feedback on
  content, as everywhere else.
- All three deferred-list builders keep only review-bot items, so a
  maintainer comment can never appear as an 'audit record'.
- The deferral note says what is actually deferred (the automated
  reviewer's non-Critical suggestions), states that maintainer
  feedback is never deferred, and names the exit (@qwen-code /retry
  opens a fresh counting window).
- SKILL.md's Critical-only policy now marks everything rendered in
  the actionable sections as in scope, so the agent does not re-refuse
  what the filter passed through.

Behavioral test updated both ways: maintainer comments/reviews stay
actionable in Critical-only mode across all three sources, bot
suggestions still defer, and structural pins hold the bypass and the
bot-only select in all six filters.

* feat(autofix): per-author feedback budget in Critical-only mode

Follow-up to the author-based split, prompted by the obvious
counterexample: a human account can host an automated reviewer loop
with the exact regeneration property the review bot has — feedback
re-generated after every push at zero marginal cost — so 'not the
bot' cannot mean 'never throttled'. An account is an accountability
unit, not a throttle; the brake has to key on measured regeneration.

Unified model: once Critical-only engages, every source has a bounded
budget of untagged feedback batches per counting window. The review
bot's budget is zero (all deferred, as before). A human's is
CRITICAL_ONLY_HUMAN_BATCHES (2) CONSUMED batches: feedback items are
bucketed into the (prev marker ts, marker ts] span that evaluated
them, only spans from Critical-only rounds count, and an author needs
K distinct consumed spans before their new untagged feedback defers.
Fresh unevaluated feedback never counts against its own author, and
the census is window-scoped, so /retry resets the budget with the
window.

The observed cases (#8037/#7944/#7885/#7799 — one or two late
verification reports each) stay fully served under K=2; a looped
reviewer is throttled after 5+K driven rounds instead of grinding to
the 100-round cap. Past the budget, continuing requires one conscious
act — **[Critical]**, a Request changes review, or /retry — which is
precisely what separates intent from automation. Over-budget authors
are named in the deferral note with those exact escapes.

Tests: the six filter replays gain over-budget cases both ways (the
tagged/CR escapes survive even over budget), and the budget census
itself is replayed over fixture files — two consumed critical-tail
batches list the author; one batch, pre-Critical batches, unconsumed
feedback, untrusted authors, and command comments never count.

* fix(autofix): fix deferred-feedback bash quoting and drop a dead jq binding (#8071)

* test(autofix): exercise census window-isolation guard with a stale-window fixture (#8071)

* test(autofix): make census command-exclusion observable; surface census stderr (#8071)

* fix(autofix): exclude never-deferrable feedback from the budget census (#8071)

The Critical-only per-author budget census counted every trusted review,
inline comment, and issue comment, including feedback the deferred renderer
would never defer: **[Critical]**-tagged comments, Request changes / APPROVED
reviews, inline replies rooted at a Critical comment, and inline comments
attached to a Request changes review. A maintainer who followed the documented
escape hatches (tag Critical, request changes) thereby spent their own budget
and had later untagged feedback silently deferred — the exact bug this PR
fixes, re-created one level down.

Mirror the three deferred-builder predicates in the census item filter so a
batch is counted only when it is actually deferrable. Extend the census replay
test with protected authors (Critical-only, Request changes, APPROVED,
Critical-rooted replies, Request-changes-review inlines, the review bot as a
trusted MEMBER, and a sentinel-ts marker probe) that each carry two
consumed-span batches yet must stay absent, so dropping any one exclusion now
fails the suite. Also fold bash's stderr into the bash -n guard assertion so a
future quoting regression reports the syntax error, not just a non-zero exit.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 11:38:40 +00:00
Shaojin Wen
4dab39c8d7
fix(autofix): answer round-cap refusals on the PR instead of only in logs (#8067)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(autofix): answer cap-gate refusals on the PR instead of only in logs

Observed on #7836: the fleet shepherd detected a merge conflict, posted
'dispatched the autofix loop to resolve it', and the dispatch died at
the scan's round-cap gate with only a log line — the PR page showed a
promise, the run showed green, and the conflict sat unhandled for
hours. Three silences stacked: the standard-management cap itself is
silent (the pause notice was takeover-only, so #7836 hit 10/10 with
zero PR-visible notice), the forced-dispatch refusal is silent, and the
shepherd dedups per head SHA — a capped PR gets no pushes, so its head
never changes and conflict handling froze permanently.

Two scan-side changes (the shepherd stays untouched — the windowed
round computation lives in the scan and duplicating it would drift):

- A FORCED dispatch (shepherd conflict lever or a human) refused at the
  cap gate now answers on the PR: cap value, what stays unhandled, and
  the two recovery commands (/retry for a fresh window, /takeover for
  the raised cap). No dedup — the shepherd sends at most one dispatch
  per head, and a human asking twice deserves two answers.
- The cap pause notice covers ALL managed PRs: the takeover variant
  keeps its wording, standard bot PRs get their own (/retry or
  /takeover). Same marker, same once-per-window dedup, same consent
  and PAT-identity checks — skip wins everywhere, and only the
  takeover variant requires the label to still be present.

After a re-arm the next scheduled scan picks the PR up normally
(conflict targets are label-independent), so the frozen-head loop
resolves without any shepherd change.

* test(autofix): replay the cap-notice consent gate across label/takeover permutations (#8067)

* fix(autofix): gate the loud cap-refusal on workflow_dispatch (#8067)

FORCED_PR is populated for every trusted pull_request_review (route emits
pr_number for those), not just workflow_dispatch, so on a capped PR each
review submission landed in the un-deduped refusal branch — 7 "Dispatch
refused" comments on #7836 where 2 carried the information. Answer only
workflow_dispatch (the shepherd lever or a human); review submissions stay
covered by the once-per-window pause notice. Adds a verbatim behavioral
replay of the guard so a dropped EVENT_NAME condition fails the test.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 09:46:45 +00:00
ytahdn
84bb09bf20
fix(web-shell): preserve approved tool results (#8099)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-30 09:01:27 +00:00
ytahdn
81367b562c
fix(web-shell): make question submission retryable (#8096)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-30 08:58:12 +00:00
ytahdn
0c0bc5e92a
fix(web-shell): add prompt send retry feedback (#8106)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-30 08:49:43 +00:00
Shaojin Wen
b64a6c4f13
fix(verify-pr): budget evidence capture in scope selection (#8104)
Post-merge measurement of #8016, on the first two live runs after it
landed (#7975 and #8066): the browser installed successfully — "Install
evidence browser: success" in both — and both reports carried **zero
images**. #7975 has 31 tables, #8066 has 407 assertions. Neither has a
witness.

The cause is the third instance of one structural bug, and it is the
same one twice already fixed:

  #7917  the /verify recommendation sat inside a section headed
         "local invocation ONLY" -> 1/16 reach
  #8016  captures were "Optionally ... when text cannot carry the
         oracle" -> 0/14
  now    captures are a required deliverable in the ARTIFACT CONTRACT,
         but the plan the agent executes is the Scope-selection budget
         list, and that list had zero mentions of evidence, png,
         capture or screenshot

An agent that budgets by that list never allocates time for a capture,
however firmly a later section says to produce one. So captures are now
budget item 4 alongside the A/B, the harnesses and the gates, with the
time reserved (~5 min), the gate named (QWEN_VERIFY_CHROMIUM=1), the
count bounded (normally two, at most a handful), and the measured
failure stated so the rule carries its own reason.

The report structure gets the matching half: the A/B table item now says
to reference the capture of those cells by filename. A produced image
with nowhere to go is as dead as an unproduced one.

Mutation-verified 4/4: dropping the budget item, the time allowance, the
count bound, or the report-side reference each turns the test red.

Not changed, and worth stating rather than implying: the seven techniques
from #8010 do not appear in either report. I do not read that as failure
— every one of them is conditional ("when one fix bundles two changes",
"when the oracle is an instrument", "when a PR adds a writer to a shared
store") and neither PR met those conditions. #8010 is not yet falsifiable
from these runs. What both reports DO show is older skill rules working:
a naive-matcher differential in #8066, and #7975's realpath check finding
node_modules/@qwen-code/qwen-code-core resolving into the HEAD tree and
then reasoning about whether that contaminates the control.

107/107 tests; prettier and eslint clean.

Co-authored-by: wenshao <wenshao@example.com>
2026-07-30 07:50:14 +00:00
ytahdn
a95510cb7c
fix(web-shell): translate legacy skills invocations (#8103)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-30 07:42:00 +00:00
Shaojin Wen
f4cd6e1d8b
fix(ci): gate the attachment guard before it allocates a runner (#8095)
Measured on a congested pool: 88 active jobs, 72 hosted and 15
self-hosted. The self-hosted 15 were all running with zero queued; the
hosted 72 were contending, and 20 of them were the SAME job —
`remove-suspicious-attachments`, all queued, none running.

Its real cost is not the work. Recent completed runs:

    queue=629s run=5s      queue=568s run=2s
    queue=518s run=2s      queue=340s run=3s

Two to five seconds of API calls behind up to ten minutes of queueing.

And almost none of it needed to happen. The trust check lived INSIDE the
github-script, so a runner was queued, allocated and started before the
job could decide it had nothing to do. Over the 200 most recent comments
on this repo: 184 from trusted associations, 9 from bots, 7 actually
needing a scan. 96.5% of these runs existed to print "Trusted author;
skipping".

Two changes:

- Hoist the association and bot checks into the job `if:`. GitHub
  evaluates `if:` BEFORE allocating a runner, so a trusted comment now
  costs nothing. The script keeps its own copies: the gate is an
  optimisation, not the control, and the two must be able to disagree
  without becoming unsafe. Every ambiguity therefore resolves toward
  RUNNING the scan — an unrecognised payload yields an empty
  association, which is not in the trusted list, so the job runs.

- Add a per-comment concurrency group with cancel-in-progress. The
  workflow listens on `edited` as well as `created`, and the bot PATCHes
  its own comments constantly, so repeated edits of one comment stacked.
  The scan reads the comment's CURRENT body, so a queued earlier scan is
  already stale and cancelling it loses nothing. (Contrast the verify
  lane, where cancel-in-progress is deliberately false because a
  cancelled run destroys evidence.) The key falls back to run_id so an
  unexpected payload gets its own group instead of serialising every
  scan into one.

Deliberately NOT moved to the self-hosted pool, though it would fit
technically (no checkout, no PR code, API calls only): the 20 stacked
jobs were duplicates, so relocating them just fills the ECS pool
instead — and that pool is what /verify and /triage depend on. It also
holds issues:write while processing untrusted comment bodies, which
belongs on ephemeral hardware rather than reused machines.

The `if:` semantics are verified against all payload shapes — 12 cases
covering both `comment.*` and `review.*` associations, bots, and
missing/empty payloads, each asserting which direction it resolves.
CONTRIBUTOR is deliberately NOT trusted: a merged PR does not make
someone's links safe.

Mutation-verified 6/6: dropping the review payload path, dropping the
bot check, adding CONTRIBUTOR to the trusted list, turning off
cancel-in-progress, collapsing the group to a global key, and inverting
the gate so untrusted comments are the ones skipped — each turns a test
red. The last is the one that matters; it is the only mutation here that
would be a security regression rather than a cost regression.

148/148 tests across both suites; actionlint exit 0; prettier and eslint
clean.

Co-authored-by: wenshao <wenshao@example.com>
2026-07-30 06:32:55 +00:00
Shaojin Wen
6b0d2bbca0
feat(triage): make /verify evidence screenshots actually possible (#8016)
* feat(triage): make /verify evidence screenshots actually possible

Measured across the 14 real verify reports on this repo: tables appear
in 14/14 (4 to 31 rows), images in 0/14. The hosting machinery has been
complete the whole time — pr-assets branch, PNG magic check, 8 images,
2 MB cap, sanitised names — and it has never once been fed.

Two independent causes, both fixed:

1. The agent physically could not produce a PNG. It runs as `node`
   under `env -i` with HOME pointed at a per-run scratch dir, so
   `playwright install --with-deps chromium` needs an apt it has no
   rights to and would re-download ~170 MB into a directory deleted at
   the end of every run. Chromium is now installed by ROOT in the tools
   step into a shared, world-readable PLAYWRIGHT_BROWSERS_PATH, and the
   agent env carries that path plus QWEN_VERIFY_CHROMIUM=1 — but ONLY
   when the install actually succeeded, so the variable's absence is a
   real signal rather than a stale promise. The install is best-effort:
   a failure warns and the run continues to a text-only report, which
   is what every run has produced anyway.

2. The skill discouraged it. Captures were "Optionally … use them when
   text cannot carry the oracle", with TUI rendering as the only
   example — so an agent verifying a daemon or a CLI correctly
   concluded text carried the oracle and skipped. It now says produce
   them whenever a harness ran, and says WHY: a table is the agent's
   claim about what happened; a capture of the run is a witness that
   the numbers came from a real execution, which is the part a reviewer
   cannot get any other way. It names the three highest-value shots
   (A/B cells side by side, the mutation matrix as printed, raw harness
   output behind a headline number) and forbids `playwright install`
   explicitly, so the failure mode of cause 1 cannot be rediscovered by
   an agent burning budget on it.

Affordable now because #8014 raised the agent budget to 120m; at 20m a
browser download alone was most of the run.

Mutation-verified 4/4: handing the agent the variable unconditionally,
failing the job on a chromium install failure, reverting the skill to
"Optionally", and dropping the do-not-install warning each turn the
test red.

90/90 tests; prettier, eslint, actionlint and shellcheck clean.

* fix(triage): match browser to lockfile and fix pr-assets branch conflict (#8016)

Split the chromium install into system deps (tools step, as root) and
browser binary (new post-checkout step, as node using the checkout's
own Playwright). This eliminates the version pin that produced an
unusable browser tree.

Change evidence hosting from a bare pr-assets leaf branch — which
cannot coexist with the existing pr-assets/* namespace — to per-PR
branches (pr-assets/pr<N>-verify) with orphan-init fallback.

Update terminal-capture skill to warn CI agents away from the
forbidden playwright install command, and close test blind spots
for conditionality (M1b), version matching (M5), and marker
ordering (M6).

* fix(triage): unpin Playwright install-deps so apt list tracks lockfile browser (#8016)

* fix(triage): assert the browser path reaches the agent, not just the flag

Review finding on #8016, verified by mutation before accepting it:
deleting `"PLAYWRIGHT_BROWSERS_PATH=$CHROMIUM_PATH"` from the agent env
left the test green. That is the nastiest arm of this feature — the
agent is TOLD chromium is available via QWEN_VERIFY_CHROMIUM=1, then
Playwright looks in the default ~/.cache/ms-playwright instead of the
shared install and every capture fails, so a successful install still
degrades to a text-only report.

The test now asserts both variables and that BOTH sit behind the
success guard. Mutation-verified 2/2: deleting the path line fails on
the containment assertion, and hoisting it outside the guard fails on
the ordering one (`expected 21928 to be less than 21834`).

Not changed, because the other two findings do not describe this
codebase — replies on the threads carry the evidence.

90/90 tests; prettier and eslint clean.

* fix(triage): cover orphan-init delivery and delete verify asset branches

Two review findings on #8016, both about the per-PR branch scheme the
autofix bot introduced on this branch (029beba3ef), and both correct.

**Orphan-init's success path had no test.** The existing scenarios both
take the clone-failed branch, but one seeded the branch (so clone
succeeds) and the other points at a non-repo (so the push fails too) —
neither proves orphan-init can DELIVER. A third scenario now points at
a valid bare remote with the branch deleted first: clone fails,
orphan-init runs, and the images land. It also asserts the branch has
exactly one commit, so a graft onto unrelated history would fail rather
than pass as "orphan". Without it, a dropped `git init` or
`remote add origin` would silently discard every image on every PR's
first verification run.

Mutation-verified: dropping `remote add origin` and dropping `git init`
each kill it. A third mutation — pointing `checkout --orphan` at the
wrong branch name — SURVIVES, and correctly: the push is
`HEAD:$assets_branch`, which names its destination explicitly, so the
local branch name cannot affect delivery. Recorded as a non-defect
rather than papered over with a test that pins an irrelevant detail.

**Per-PR asset branches were never deleted.** `pr-assets/*` had exactly
one producer and one deleter; the verify lane became a second producer
without being added, so every verified PR would leave a single-commit
branch in the base repo permanently. The cleanup workflow now deletes
both refs, is renamed to match what it does, and carries a note that
every new `pr-assets/*` producer must be added — a branch nothing
deletes is permanent.

The loop deliberately drops `set -e`: most PRs produce neither branch,
so a 404 on the first must not skip the second. A real delete failure
still warns and sets a non-zero exit. The new test executes the script
against a stubbed `gh` where the visuals branch 404s, and asserts the
verify branch is still deleted — which a `set -e` version would fail.

92/92 tests; prettier and eslint clean.

* fix(triage): match browser install to the harness Playwright and re-arm the strip guard (#8016)

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

* fix(triage): gate chromium marker on deps and resolve Playwright from the harness (#8016)

Address review feedback on the verify evidence-browser path:

- Resolve the Playwright CLI from the capture harness's own directory
  (require.resolve of the exported package.json, then join cli.js) instead
  of assuming npm hoists playwright to the root, so the installed binary
  tracks the harness's import even if the hoist layout changes. cli.js is
  not in the package's exports map, which is why a direct path was used
  before; resolving the exported package.json keeps that bypass while
  removing the hoist assumption.
- Gate the verify-chromium-path marker on a new verify-chromium-deps-ok
  marker written by the tools step. apt and the Playwright CDN are
  independent servers with no shared success signal, so a binary download
  alone must not promise chromium to the agent when system deps failed.
- Add a delete-failure case to the PR asset cleanup test so the status=1
  and exit path is executed, not just statically asserted.

* fix(triage): strip cache creds from browser install and guard cli.js resolution (#8016)

The evidence-browser install runs the PR-resolved Playwright CLI but did
not drop ACTIONS_RUNTIME_TOKEN/URL/CACHE_URL the way the adjacent prepare
and agent steps do, leaving runner-injected cache credentials readable by
PR-controlled code. Strip them in the same env -u chain. Also replace the
string-only guard on the require.resolve + cli.js join with an execution
guard that resolves the expression against the installed tree, so a
Playwright bump that relocates cli.js fails the test instead of silently
degrading evidence capture at runtime.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 05:37:42 +00:00
ytahdn
0ae7757b0b
fix(web-shell): improve artifact previews (#8078)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
2026-07-30 05:07:24 +00:00
Shaojin Wen
cc2cddd418
fix(web-shell): show server queue status for pending messages (#8065)
* fix(web-shell): show server queue status for pending messages

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

* fix(web-shell): hide insert for server-queued prompts

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 04:26:48 +00:00
Shaojin Wen
cfcc711a70
feat(autofix): back off scan inspection of idle candidates (#8049)
* feat(autofix): back off scan inspection of idle candidates

The scheduled scan inspects every candidate every tick. The takeover
pool doubled in two days (28 open takeover PRs, 8 of them idle in
'nothing new' state for 10+ hours), and idle candidates crowd the two
SHARED budgets: MAX_CANDIDATE_INSPECTIONS (60) and the 10-target cap.
Observed on #8002: freshly engaged, admitted by the 09:03 scan, then
deferred by the target budget while long-idle PRs re-confirmed their
idleness yet again.

Candidates whose list-provided updatedAt (no extra API call) is older
than 24h are now inspected on roughly every 4th scan, on a
deterministic slot keyed by PR number and UTC hour so no PR waits
forever. The skip is free — it sits with the busy skip before the
inspection-budget increment.

Safe by construction: every real wake-up bumps updatedAt (reviews,
comments, labels, pushes) or routes in real time anyway, so the only
thing deferred is the scheduled re-confirmation of idleness plus
worst-case a few hours of base-conflict-detection latency for a PR
nobody touched in a day. The forced-dispatch path never builds the
list files, so a forced PR is always inspected.

* feat(autofix): idle-backoff review follow-ups

- Corrected the comment's cost model: idle candidates hit 'continue'
  before the TARGETS append, so they never contend for the 10-target
  cap — the real win is the shared inspection budget plus the serial
  scan-walk latency (the walk is what delayed #8002's pickup by ~6
  minutes), and the comment now says exactly that.
- Slot quantum changed from the hour to the scan tick (600s, the same
  quantum as ROT_OFF): an hourly slot against the */10 cron meant 6
  back-to-back inspections then a ~3h blind window per PR — same 25%
  average, terrible shape. The gap is now bounded at ~30 minutes, which
  is what the operator-facing strings promise ('gap ≤30m').
- The two scan-only signals updatedAt cannot see (a base conflict
  appearing when main moves; still-red checks awaiting the redcheck
  marker) are named in the comment instead of papered over.
- The per-candidate jq fork became a single precomputed set + a bash
  substring test, matching the busy skip's idiom and the 'free' claim.
- Tests: the skip predicate and set builder got a behavioral replay
  (idle+out-of-slot defers, idle+in-slot inspects, fresh inspects,
  missing-from-lookup inspects); the two byte-distance assertions
  became a loop-head slice (comment growth cannot red-light CI, and
  budget-consuming code between the skips and the increment fails);
  the --json field pin is order-independent; the 3600 quantum is
  pinned OUT.

* test(autofix): pin the null-updatedAt defensive guard in idle-backoff replay (#8049)

* fix(autofix): extract idle-backoff predicate from workflow, fix gap bound 30→40m (#8049)

* test(autofix): pin takeover-prs.json in idle-backoff replay (#8049)

* fix(autofix): state idle-backoff gap probabilistically, unpin quantum (#8049)

Round-2 verification showed the scheduled scan lands every ~40-70 min on
this repo, not every 10 min, so the (epoch/600)%4 slot is an independent
~25% draw per scan, not a deterministic 1-in-4 rotation. The gap is
geometric (measured median ~2h, p90 ~6h), not bounded at ~40m.

Reword the operator-facing strings (skip echo, fleet row) and the workflow
comment to state the behaviour probabilistically and drop the false
"bounded" / "no PR is unlucky forever" claims; correct the misattributed
#8002 latency (queue/startup, not the serial walk). Relax the tests so a
truthful number is not a CI failure: pin the mod-4 time-quantum shape
instead of the exact 600s constant, and drop the /3600 exclusion that
forbade the better-tailed quantum. Mechanism logic is unchanged.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 04:26:43 +00:00
qwen-code-dev-bot
7db57552e3
fix(integration): make interactive read-then-write test deterministic (#8060) (#8064)
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
* fix(integration): make interactive read-then-write test deterministic (#8060)

Drive the multi-turn conversation with the fake OpenAI server instead of a
live LLM. The real model made this test flaky on main: it could choose
different tools, phrase the read result without the literal version, or
settle the first turn on its own schedule. Scripting the exact read-then-write
turns keeps the interactive mechanics (typed input, tool execution, file
mutation) under test while removing the nondeterminism, matching the
fake-server pattern already used by the other multi-turn interactive tests.

* fix(integration): add debug output to read-step assertions (#8060)

* test(integration): clarify scripted-echo assertion and verify request count (#8060)

* test(integration): ground version assertion in the read result (#8060)

Address review feedback: assert the read_file tool result the CLI sent
back (requests[1]) contains '1.0.0', so the version check observes the
real read result rather than only the fake model's scripted echo. Also
correct the flake rationale comment to cite the verified mid-stream
stall on turn 2 instead of an unsupported late-settle hypothesis.
2026-07-30 03:06:24 +00:00
易良
f005f3eee8
feat(github-channel): add transient working reaction (#8061)
* feat(channels): acknowledge GitHub requests

* fix(channels): remove transient GitHub reactions

* test(github-channel): cover reaction failures

* test(github-channel): cover pending reaction dedup

* docs(github-channel): clarify final-only output

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 03:00:29 +00:00
ytahdn
d328dd3299
fix(web-shell): stabilize enhanced table controls (#8041)
* fix(web-shell): stabilize enhanced table controls

* fix(web-shell): stabilize table detail scrolling

* fix(web-shell): preserve fixed table widths

* fix(web-shell): address review feedback on enhanced table controls (#8041)

* fix(web-shell): remove dead branch and cover mixed column sizing (#8041)

* test(web-shell): cover frozen-shadow resize fallback and pin column-width divisor (#8041)

* refactor(web-shell): remove cell widths ignored under fixed table layout (#8041)

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 02:52:35 +00:00
qwen-code-dev-bot
4558bfa725
test(e2e): force delegation in flaky subagent case (#8070) (#8073)
The main agent runs in yolo mode with read_file registered, so the model sometimes read the file directly instead of delegating, failing the foundTaskTool assertion. Make the prompt require delegation and forbid direct reads, and make the subagent imperative about calling read_file. Tool restriction (coreTools/excludeTools) is not viable: the subagent shares the session permissionManager, so it would lose read_file too.

Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-30 02:44:54 +00:00
Shaojin Wen
d5ac7a9642
fix(autofix): post the takeover engage ack from the command itself (#8043)
* fix(autofix): post the takeover engage ack from the command itself

The engage ack rode a pull_request:labeled round-trip: takeover-command
applies the label, the labeled event routes, and the takeover-ack job
posts the confirmation. That event has now been observed to simply not
fire twice in one day (#7999 — the author read the silence as failure
and removed the label; #8002 — an engaged fork PR with no ack for
hours), and fork label events can never ack at all since they carry no
secrets: a fork /takeover stayed silent until the next scan picked the
PR up (2h41m on #7993).

takeover-command now posts the engage ack directly after applying the
label — every admission gate has already passed at that point, so
'engaged' is truthful for in-repo and fork PRs alike; the fork variant
adds the expectation that the first round comes from the next scheduled
scan. The route side suppresses the label-path ack when the label
sender is the bot (only the ack: the immediate scan still routes), and
the review-scan's existing first-pickup ack dedups against the
command's comment and heals it if the post failed.

Two more silent paths become audible while here: a /takeover on a
stacked (non-main-base) PR now refuses out loud instead of dropping
with only a log line, and a /takeover stop on a non-main PR now
proceeds to remove the label instead of leaving it stuck.

* fix(autofix): ack command-driven releases directly and key the scan grace on the label actor

Review follow-up: the engage-side fix left the release side on the
fragile round-trip — a loud add next to a mute stop re-creates the
exact 'did it work or did the event get lost?' ambiguity this PR set
out to remove, now on release. And a fork or non-main release could
never ack at all (fork unlabeled events carry no secrets; the route
ignores non-main releases).

- takeover-command now posts the release ack directly after removing
  the label, mirroring the engage side, with the same three variants as
  the ack job (plain release / bot-authored / bot-authored+skip) chosen
  from the same PR_INFO the gates used. The route suppresses the
  unlabeled-path ack when the label sender is the bot.
- The scan's first-pickup grace is keyed on the label event's actor:
  a bot-applied label defers only 45s (the command's own write is
  seconds behind the label — fork or in-repo alike), so the immediate
  scan still heals a failed command ack instead of slipping to the next
  scheduled tick, and an ic.json snapshot taken between the label write
  and the command ack cannot double-post. A human in-repo label keeps
  the 3-minute ack-job grace; a human fork still posts right away.
- The fork note now says 'usually within minutes', matching the
  */10 cron instead of contradicting it by 6x.
- Tests kill the two surviving mutations from review: the EN/ZH
  fork-note swap (per-language-half assertions) and the warning
  fallback downgraded to '|| true' (both fallback strings pinned).

* test(autofix): behaviorally pin the LAST_LABELED_BY jq extraction (#8043)

* fix(autofix): correct stale comments about scan heal timing and command blast radius (#8043)

* test(autofix): pin release-ack body identity across the command and ack jobs (#8043)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-30 02:41:08 +00:00
Shaojin Wen
c631c0448e
fix(autofix): cumulative timeout breaker, narrowed retry prompt, truthful handoff wording (#8044)
* fix(autofix): stop repeat-timeout PRs with a cumulative breaker and narrow the retry prompt

The consecutive-failure cap resets on every pushed round, so it never
fires on the observed failure shape: timeouts interleaved with
successes. #7929 burned three 50-minute agent timeouts (with pushed
rounds in between) and #7846 two — each a full agent budget with
nothing to show, invisible to the breaker by construction.

Three changes:

- Cumulative timeout breaker (TIMEOUT_WINDOW_CAP=3): time-budget
  exhaustions are counted over the whole counting window, successes in
  between included. At the cap the PR goes terminal with a
  skip-until-/retry headline, exactly like the consecutive breaker. The
  census reuses PRIOR_HEADS, so it is window-scoped and a re-arm
  clears it.

- Prompt narrowing from the second attempt: when the current window
  already contains a timeout marker, the prepare step appends a budget
  warning to feedback.md — smallest blocking subset first, commit
  early, defer the rest explicitly — instead of re-running the
  identical address-everything prompt into the same wall.

- Truthful gate-rejection handoff: 'A human should take over this PR'
  read as a full release, but the loop keeps managing the PR (new
  feedback, base conflicts) — #7929 posted it and then kept pushing
  rounds. The headline now says exactly which half is over: this item
  will not be retried automatically, management continues.

* fix(autofix): timeout-breaker review follow-ups

All six review findings addressed:

- The narrowing prompt now restates the SKILL deferral contract instead
  of pointing at 'your summary': deferred findings stay out of
  resolved-comments.txt and get a comment-replies.json entry so every
  open thread carries its reason — the summary-only shortcut is exactly
  what a budget-pressured agent would otherwise take. 'skip refactors
  entirely' became 'decline with a one-line reason' for the same
  never-drop-silently rule.
- The narrowing census counts timeouts SINCE THE LAST SUCCESSFUL ROUND,
  not cumulatively: a push falsifies 'not converging', and one old
  timeout must not degrade every later round of a 100-round window. The
  breaker stays cumulative (a push does not make the next timeout
  cheaper in budget terms).
- The breaker headline states what the census measured ('this counting
  window now contains N time-budget exhaustions') instead of 'stopped
  after N' — the tripping round can itself be a gate rejection, which
  is the exact rollout state of #7929/#7846.
- Both census needles anchor on the verbatim emitted headline ('AutoFix
  ran out of time before finishing') — first lines can embed provider
  error text, so the loose phrase could count a model error as a
  timeout.
- The precedence assertion got teeth: 'consecutive' alone matched both
  branches; it now asserts the consecutive breaker's own phrase and the
  absence of the timeout one (the if-true guard mutation now fails).
- The narrowing census got a behavioral replay over fixture ic.json:
  push-resets, trailing counts, legacy no-win markers under 'none',
  old-window exclusion, author filtering — plus a pin on the -ge 1
  trigger so a threshold mutation cannot leave the feature inert.

* fix(autofix): state what the timeout breaker measured, pin the inherited guarantees

Second-round review follow-ups:

- The breaker headline no longer infers 'is not converging' — a window
  can carry 40 pushes and still trip the (deliberately cumulative) cap,
  so the sentence now states only what the census measured: N full
  agent runs that pushed nothing.
- Two guardrail tests pin behavior that held only by construction: a
  non-timeout failure landing on an already-capped window still trips
  the breaker (the documented rollout state — an 'only count when this
  round timed out' cleanup would silently delete it), and the
  transient-API-error exemption inherited from the outer guard (a
  refactor hoisting the block out of it must not mass-terminate every
  in-flight PR during a provider outage).

* fix(autofix): name a gate rejection only when the gate ran; pin the timeout breaker's stale-base exemption

Third-round review follow-ups:

- The handoff headline no longer claims 'the verification gate rejected
  the attempt' for every outcome=failed verdict: that branch is reached
  for five distinct failures (failure.md abort, dirty tree, unchanged
  branch, missing summary, and a real gate rejection), only one of which
  is a gate decision. The clause is now gated on gate-rejection.md,
  which reject_fix is the sole writer of — so the other four paths keep
  the neutral framing instead of repeating the very
  wording-doesn't-match-behaviour bug this PR fixes. A replay test pins
  both halves: the clause appears when gate-rejection.md exists and is
  absent when it does not.
- The timeout breaker's stale-base exemption is now pinned by a replay
  (five in-window timeouts plus a stale-base retry current round stays
  retryable), matching the existing transient-API-error pin: a refactor
  hoisting the timeout block out of the outer guard would otherwise
  delete this exemption silently.
- The prepare-step narrowing census jq now fails open (2> /dev/null ||
  true) like its report-step sibling, so a malformed ic.json drops the
  budget warning instead of aborting prepare under errexit and turning
  the round into a terminal 'could not start evaluation' handoff.
- The decay comment now matches the code: the warning resets on a
  push/no-op round but fires on every failing round until then (gate
  rejections included), which is correct since nothing has converged.

* fix(autofix): clear stale gate-rejection.md in repair cleanup and pin headline template (#8044)

* test(autofix): cover no-op reset branch in timeout census (#8044)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 02:41:00 +00:00
Shaojin Wen
25f5147677
feat(autofix): post a takeover milestone digest every tenth pushed round (#8046)
* feat(autofix): post a takeover milestone digest every tenth pushed round

The takeover round cap (100) bounds runaway but carries no signal about
when a human should step in: #7469 ground to round 12 over seven days
of takeover with fifteen pushes, and the only place that trajectory was
visible was the Actions logs.

Every 10th pushed round under takeover, the report step now posts a
window-scoped census on the PR itself — pushed fixes, no-change
reviews, timeouts, rejected attempts, base updates — plus the three
options (keep going / split or reduce / release takeover). The digest
is a separate comment with its own autofix-milestone marker and no
autofix-eval marker, so every census (round, consecutive-failure,
watermark) ignores it and the feedback filters keep it out of the
agent's prompt. Posting is best-effort: a digest failure never fails a
good push.

The rejected-attempt count matches both the current and the reworded
gate-rejection headline so the census cannot silently zero; base
updates carry no win= field and are windowed by timestamp instead (the
window key is the engage ack's created_at).

* feat(autofix): milestone digest review follow-ups

- Residual bucket: every outcome the four buckets missed (crash, model
  error, gate error, infra) now lands in 'other round(s)' — a window
  that burned 80% of its budget on crashes was rendering as four zeros,
  QUIETER than a healthy window, the inverse of the digest's premise.
- Crossing trigger: fire on the first pushed round once 10+ rounds have
  accumulated since the last digest in this window, instead of an exact
  %10 hit — failure rounds advance the counter too, so push@9/crash@10/
  push@11 skipped the digest forever on exactly the failure-heavy PRs
  it exists for.
- The success log is chained to the post; a failed comment no longer
  logs 'posted' after its own warning.
- WINDOW=none says 'since the PR opened (no counting window yet)'
  instead of claiming a current window while counting all time.
- A census that parses zero window markers at round 10+ skips the
  digest instead of posting a fabricated all-zero one.
- autofix-milestone joins BOT_COMMENT_FILTER (marker inventory).
- The timeout needle anchors on the verbatim emitted headline.
- The pin-only test became a behavioral replay: the digest block runs
  under bash with a stubbed gh over fixture ic.json histories — bucket
  counts, residual loudness, crossing suppression/resumption, old-window
  isolation, none-window phrasing, non-pushed/non-takeover gating, and
  the empty-census skip. The OUTCOME == fixed conjunct is pinned.

* test(autofix): cross-pin digest census needles to headline emission sites (#8046)

* test(autofix): widen rejected-headline cross-pin to match #8044 reword (#8046)

* test(autofix): behaviorally cover the digest comment-failure branch (#8046)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 02:40:55 +00:00
Shaojin Wen
36fe53d421
feat(triage): raise the /verify agent budget from 25m to 120m (#8014)
* feat(triage): raise the /verify agent budget from 25m to 120m

Maintainer decision: give /verify enough time to do what a maintainer's
local round does, and add machines if the pool gets tight.

Three values encode this one budget and each has its own silent failure
when edited apart, so all three move together:

- agent kill      25m  -> 120m   (the graceful budget; ships a partial
                                  report on expiry)
- watchdog secs   1500 -> 7200   (distinguishes that kill from an OOM;
                                  left low, a late 137 is mislabelled
                                  `timeout` and publishes "partial
                                  evidence" for a crash)
- job timeout     60m  -> 150m   (guards infra hangs only; it must clear
                                  agent + install/build + overhead or it
                                  kills the container mid-run and the
                                  ship-what-ran path never executes)

150 rather than 130: measured install+build is ~6m (run 30284341325,
npm ci 3m00 + build 2m40), budgeted at 15m for a cold cache, plus ~5m of
tools/checkout/pin/upload/cleanup — worst case ~140m, leaving 10m of
headroom.

The skill's advertised budget moves too, and this is the part that
actually changes behaviour: it read "≈ 20 minutes (hard 25-minute
kill)", and an agent obeying that would have self-limited no matter what
the workflow allowed. It now reads 110/120 and says what the extra time
is FOR — bisecting a threshold through the real code path, compiling an
intermediate build to separate the halves of a bundled fix, adjudicating
mutation survivors, driving a real daemon end to end. It also says
plainly that spending it on breadth is the one way to waste it; the
rule that one proven load-bearing claim beats ten unverified
observations does not relax because the clock did.

Pinned by a new test that asserts the RELATIONSHIPS rather than the
numbers: watchdog == agent budget in seconds, job >= agent + 20m, and
the skill's advertised hard kill == the workflow's, with the soft budget
strictly below it. Mutation-verified 4/4 — leaving the watchdog at 1500,
the job at 60, the skill at 25m, or raising the soft budget above the
hard kill each turn it red.

Cost, stated so it is a decision and not a surprise: a verify run now
holds one ECS slot for up to 2.5h instead of 1h. Concurrency is
unchanged (per-PR group, cancel-in-progress false), so this reduces how
many distinct PRs can verify at once, not how many runs a PR can queue.

90/90 tests; prettier, eslint and actionlint clean.

* fix(ci): pin soft-budget lower bound in verify consistency test (#8014)

* fix(ci): harden verify budget guard and clarify budget is a ceiling (#8014)

* fix(ci): measure elapsed before artifact copy and tighten budget guard (#8014)

* test(ci): pin elapsed-time chain in verify budget drift guard (#8014)

* refactor(ci): derive verify watchdog threshold from the agent budget (#8014)

The watchdog threshold that tells a graceful timeout (137 at the budget)
from an OOM kill (137 before it) was a bare `7200` coupled to the `120m`
agent timeout only by a comment. Define the budget once as AGENT_BUDGET_M
and derive both the timeout (`${AGENT_BUDGET_M}m`) and the threshold
(`$((AGENT_BUDGET_M * 60))`) from it, so editing one cannot silently
desync the other. The consistency test now asserts that derivation rather
than re-extracting two independent literals.

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 02:40:34 +00:00
Shaojin Wen
0232e73811
feat(verify-pr): add seven techniques from maintainer verification rounds (#8010)
* feat(verify-pr): add four techniques from maintainer verification rounds

Two hand-written maintainer rounds contained methods the skill could not
have produced. Checked each against the current text before adding it;
these four had no coverage at all.

From #7914 (live daemon A/B on the artifact-recording change):

- Run every control on BOTH arms. That round's sharpest finding came
  from a control whose only job was to validate the BASE probe — "the
  empty list is a real absence, so have the model call record_artifact
  and watch an entry appear". Run on head as well, it showed the
  curated title being silently discarded. The control was not hunting
  for a bug; running it symmetrically is what found one.

- A new writer into a shared store is an ordering change. The PR added
  write_file as a second writer into the artifact list; the bug was not
  in the new writer but in the collision, where a pre-existing
  first-writer-wins merge began discarding record_artifact's curated
  title and description while still reporting success. Enumerate the
  other writers, exercise the collision in both orders, and check what
  the loser is told — and separate the pre-existing cause from the PR's
  contribution so the author is not blamed for the policy.

From #7998 (ink cursor fix, real-terminal A/B):

- When the oracle is an instrument, corroborate it with a mechanism
  that does not use that instrument. The hardware cursor row came from
  `tmux display-message -p '#{cursor_y}'`, then from a marker printed
  after the TUI exits — which lands wherever the cursor actually was.
  Two agreeing instruments turn a measurement into evidence; one tool's
  report about the system is not the system.

- Re-run the generator on committed generated artifacts and diff. That
  round re-ran `npx patch-package ink` and found byte-different hunk
  headers, proving the .d.ts hunks were hand-written rather than
  regenerated as the description claimed.

Also strengthens Not covered: proving a limitation is environmental
requires an A/A control (boot base and head identically, show both fail
the same way), because "seems environmental" and a real regression look
identical in a report.

Mutation-verified 4/4, each with landing proof. Two initially reported
`landed: False` — the assertions match the whitespace-normalised text
while the rules wrap across lines in the source, so the replace never
fired and the green result proved nothing. Re-run against line-accurate
anchors, both kill.

89/89 tests; prettier and eslint clean.

* feat(verify-pr): teach the timing-race and scenario-arrival checks

Third maintainer round mined for method (#7934 R4). The blocker it found
had zero coverage in the skill — `timer`, `wall-clock`, `flake`,
`retry`, `duration`, `deterministic` all returned 0, and the one `race`
hit was a substring of "trace".

- **Timing-triggered assertions have a threshold — measure it, do not
  sample it.** A new guard (`expect(false).toBe(true)` after an abort
  loop) turned a vacuous pass into a deterministic failure, because the
  case triggers its abort from `setTimeout(..., 1000)` while the query's
  duration is set by CLI startup rather than the server. Natural
  completion measured 730-2151 ms, so every box on the fast side of
  1000 ms fails. The rule says to measure the operation's natural
  duration with the trigger disabled and compare it to the timer,
  because a green run only proves this box was slow enough.

- **A speed-correlated failure is not flake, and a retry budget does not
  absorb it.** Random flake becomes a pass under `retry: 2`; this failed
  5/5 runs on all three attempts. The two get opposite verdicts, so the
  kind has to be established before the verdict is written.

  Stated plainly in the skill: the verify job runs on a shared, loaded
  runner — the regime where such a test PASSES. Repetition cannot
  reproduce a fast-machine failure there; only computing the margin can.
  A rule that said "run it more times" would be useless in this lane.

- **The failure one level before vacuity: the scenario never reached the
  code under test.** The vacuity check asks whether an assertion can
  fail; this asks whether the code ever ran. Four abort cases fired
  during CLI process startup, so the fake server saw zero requests and
  a suite named for mid-stream aborts never streamed — with every
  assertion green. Instrument the seam and assert the count is
  non-zero.

Mutation-verified 5/5, each with landing proof against line-accurate
anchors.

89/89 tests; prettier and eslint clean. Skill is 472 lines, up from 392.

* feat(verify-pr): six more techniques, from three maintainer rounds

Mined #7836 R2, #7885 and #7899 for method. Checked each candidate
against the current text first; six had zero coverage, the rest were
already there (harness teeth-checks, pin dereferencing, boundary
probing, and the follow-up round's "re-measure, never diff").

The one that corrects the skill's own core method, from #7836:

- **Before calling a survivor vacuous, escalate to a finer mutation.**
  A whole-file revert is blunt enough to remove the PRECONDITION a test
  depends on, so a good test goes green because its scenario no longer
  occurs — from the outside, identical to a test that asserts nothing.
  A `finally`-cleanup test survived reverting four production files and
  died to deleting one line. Coarse survived + fine killed ⇒ the test
  is fine and the mutation was wrong. A false "your test is vacuous"
  costs the author more than a missed survivor does.

From #7836, the root cause shared by both of its blockers:

- **When the same predicate is checked in two places, verify they see
  the same state.** A guard duplicated across a process boundary is two
  implementations of one question that diverge when their INPUTS differ.
  One settings key made a route ask sessionExistsInAnyState() with an
  unpinned runtime dir while the child asked with a pinned one, turning
  a clean 409 into a 500 plus a process.exit(1) that killed every
  session on the channel. Includes the temporal half: lazily-created
  backing files leave a window where a just-created entity is invisible
  to any on-disk existence check.

- **Measure the blast radius on bystanders.** The caller's own error
  code understates a shared-state failure; the number that matters is
  an unrelated session going 200 -> 404 and a workspace list going
  2 -> 0.

From #7885, which took a performance claim apart:

- **Isolate the slice the mechanism can actually affect.** A speedup
  claim is two claims: the mechanism works, and the thing it speeds up
  matters. `--ignore-scripts` isolated what an npm download cache can
  touch — 36s of a 226s install — so the ceiling was 20s and the real
  saving 15%, not the claimed 75%. Then check it against the whole job:
  33s off 14m37s.

- **A mechanism that persists something has a cost — price it.** 219 MB
  per lockfile hash into a pool at 9.98 GB of a 10 GB cap, with 39
  distinct lockfile states in 30 days: at the cap every entry evicts by
  LRU, including entries other jobs need and its own.

- **Test the scarier consequences and report which do NOT hold.** The
  write-path finding was real; code injection was disproved (npm
  integrity-checks a tampered cache and refetches) and privilege
  escalation was disproved (chown -R does not follow symlinks). A
  finding that names what it is not is harder to wave away.

- **Verify third-party actions from their own manifest.** The PR said
  the cache dir was discarded after the job; `action.yml` declares
  `post: dist/save/index.js` with `post-if: success()`, which uploads
  it as root with credentials intact — the opposite of the claim, and
  the whole finding.

From #7899:

- **To exercise real production data safely, interpose a refusing proxy
  on the write path.** Wrap the client so every mutating call hard-
  fails, then run the shipped script verbatim: real counts, mechanical
  guarantee of no side effects.

Mutation-verified 9/9, each with landing proof against line-accurate
anchors. 89/89 tests; prettier and eslint clean. The skill is now 546
lines, up from 392 — the growth is deliberate and called out in the PR
body's risk section.

* feat(verify-pr): decomposed fixes, contextual limits, destination counts

From #7862 R4. Three additions, and a deliberate stop.

- **When one fix bundles two changes, build the intermediate variants.**
  An A/B against base proves the pair works and says nothing about what
  each half does. That round compiled a third build with only the
  ordering change reverted, and the three-row table showed the halves
  do different jobs: moving `initialized = true` after the fallible work
  converts a 2,999-and-climbing backlog flood into a fail-safe retry,
  while `reduce()` restores liveness. Either alone leaves a channel that
  floods or wedges — a conclusion the two-cell A/B cannot reach.

- **A limit measured in isolation does not transfer to the real call
  site.** The same `Math.max` spread threw between 110k and 130k
  elements inside a deep async stack, well below a standalone
  micro-benchmark. Bisect thresholds through the real code path and
  quote the harness; a limit taken from documentation or a toy loop is a
  guess about the system under test.

- **Count at the destination, not at the component boundary.** The
  mirror of the scenario-arrival rule added earlier: envelopes the
  adapter emitted and prompts that reached the agent are different
  numbers, and every gate lives between them. A count taken at the seam
  can be right while the feature is silently dropped downstream.

Declined from the same report, to protect prompt budget rather than
because they are wrong: siblings-as-convention-oracle (the lockfile
version table across five channels), degenerate fixtures that cannot
distinguish two sort keys, and naming the condition under which a
cosmetic finding becomes real. Each is a good technique; none is worth
another rule competing for attention with the ones already here.

The skill is now 578 lines, up from 392 on main (+47%) across this
branch. That growth is the main risk on this PR and further additions
should wait until a live round shows the current set changes behaviour.

Mutation-verified 3/3 with landing proof. One mutation initially
SURVIVED — it deleted text sitting AFTER the asserted phrase, so the
assertion still matched and the green proved nothing; re-run against
the phrase itself, it kills.

89/89 tests; prettier and eslint clean.

* test(scripts): drop stale technique count from verify test name (#8010)

* fix(triage): correct verify-skill worked examples and verdict path (#8010)

Address review feedback on the verification-techniques skill:

- Make the npm-cache worked example's numbers close: separate the 20 s
  download-slice ceiling (36 s to 16 s) from the 15% end-to-end saving
  (226 s to 193 s) rather than conflating them.
- Stop overstating the tarball experiment: one tarball was poisoned, and
  the 2262-entry integrity coverage is a separate static fact.
- Give the speed-correlated-failure rule a contract-legal verdict path by
  encoding the margin as a scripted assertion, and mark the load/idle
  sweep as the local-mode variant.
- Fix the one bullet that broke its 2-space list continuation.
- Pin the new contract-encoding clause in the workflow test.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 02:40:26 +00:00
Shaojin Wen
19761af072
fix(cli): stamp QWEN_CODE_CLI at the workspace entry and publish QWEN_CODE_MODEL (#7993)
* fix(cli): stamp QWEN_CODE_CLI at the workspace entry and publish the active model as QWEN_CODE_MODEL

Skill subprocesses shell out through `"${QWEN_CODE_CLI:-qwen}"`. The npm entry
(scripts/cli-entry.js) stamps QWEN_CODE_CLI, but the workspace entry
(packages/cli/dist/index.js) never did — so a dev run, or any direct
`node dist/index.js` launch, leaves the variable unset and every review
subcommand the /review skill issues silently lands in whatever global `qwen`
PATH resolves to. Measured on a live run: a freshly built CLI's review pipeline
executed entirely on a global v0.21.0 — `script-lint` did not exist there, so
the deterministic gate the skill expected was silently absent, and any
behavioral fix to the review CLI is inert in such runs.

Stamp the entry in runCliEntryPoint, first-writer-wins: an outer launcher
(cli-entry.js, the desktop shim) has already stamped in-process and must keep
winning; an empty value counts as unset, matching the consumer's `:-`
semantics. The entry is derived as `../index.js` from the compiled
dist/src/cli.js — the shebang-bearing bin — and skipped entirely for non-file
schemes (vitest) and unbuilt layouts (tsx dev runs keep today's fallback).
tsc emits dist/index.js as 0644 and the spawn-time filter blanks a
non-execable entry, so the stamp grants 0o755 best-effort; a failed chmod
degrades to today's `qwen` fallback.

Separately, subprocesses had no authoritative way to learn the ACTIVE model:
the /review skill's compose step wants a modelId, and the orchestrator resorts
to reading settings files — wrong under QWEN_HOME isolation and after /model
switches (measured: a report stamped with a model that never ran the review).
Publish QWEN_CODE_MODEL exactly the way QWEN_CODE_SESSION_ID is published: the
first Config claims the process-global slot, only the claiming instance
republishes (on refreshAuth and every model-change notification), and
getShellContextEnvVars passes it through, omitted when absent. The daemon
limitation is the session ID's own — later sessions read the first session's
model — and is documented at both the producer and the consumer.

* fix(core): clarify QWEN_CODE_MODEL daemon comment and cover refreshAuth republish (#7993)

* test(cli): cover stampCliEntryEnv wiring in runCliEntryPoint (#7993)

* fix(core): publish QWEN_CODE_MODEL per session and preserve entry mode on stamp (#7993)

Address review feedback:

- Key QWEN_CODE_MODEL on the session (registerSessionModel/getSessionModel),
  mirroring the project dir, so daemon-mode subprocesses read their own
  session's active model instead of the first session's. The process-global
  slot remains as the single-session CLI fallback. This also neutralizes the
  order-dependent claim: a throwaway Config's registration is keyed under a
  session id no real spawn resolves.
- stampCliEntryEnv now adds exec bits to the existing mode (mode | 0o111)
  rather than setting 0o755, so a private 0o600 checkout becomes execable
  without becoming world-readable.
- Cross-reference scripts/dev.js and scripts/start.js in the stamp doc comment
  and note the bundled `node dist/cli.js` launch is intentionally not stamped.
- Widen the AuthType test mock to include QWEN_OAUTH, pin the stamp-before-run
  ordering in the wiring test, and cover the per-session model lookup.

* fix(cli): correct comment on Vite rewrite mechanism in protocol guard (#7993)

* fix(core): re-key per-session model registry on startNewSession (#7993)

startNewSession minted a new session id and re-stamped QWEN_CODE_SESSION_ID
but left the per-session model registry keyed on the outgoing id. After
/clear (or /reset, /new, /resume) a non-owner Config's subprocesses then
resolved the model by the new id, missed, and fell back to another
session's value. Unregister the old entry and republish under the new id.

Also correct the stampCliEntryEnv comments: npm start / npm run dev route
through scripts/start.js and scripts/dev.js, which stamp QWEN_CODE_CLI
themselves, so the only uncovered launcher is a direct node dist/index.js.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@alibaba-inc.com>
2026-07-30 02:40:18 +00:00
Shaojin Wen
0a9db38221
feat(review): add review run — headless review with a machine-readable verdict (#7983)
* feat(review): add `review run` — headless review with a machine-readable verdict

The review pipeline already runs non-interactively: `qwen --prompt "/review …"`
expands the bundled skill, launches the dimension agents, and honors the
approval mode. What that path lacks is a contract. The verdict lives in the
model's prose and in files whose names the caller must simply know, the exit
code says nothing about the outcome, and piped stdin silently defeats
slash-command detection (the runner prepends piped input, so the leading `/` is
no longer first). Anyone who wants "run a review, tell me what it decided" ends
up scraping a terminal.

`qwen review run [target]` is that contract and nothing more. It assembles the
/review invocation from typed flags (--effort, --comment), re-enters this
build's own CLI in a child process with stdin closed, streams the child's
progress to stderr, and then reads the verdict from the artifact compose-review
wrote — the same JSON the skill treats as the verdict authority — never from
anything the model printed. stdout carries only the result (human lines, or the
full JSON with --json).

Exit codes make the outcome scriptable without parsing: 0 = the review
completed (whatever it decided), 1 = it never reached a verdict (child failure,
timeout, or no composed artifact — a clean child exit without one is a run that
wandered off, not an approve), 3 = completed AND --fail-on request-changes AND
the event is REQUEST_CHANGES, so a CI gate can tell "blocking verdict" from
"the tool broke".

Artifact discovery is scoped to this run (mtime cutoff with a small slack for
coarse filesystem clocks): a stale composed JSON from an earlier review says
whatever THAT review decided, which is exactly the wrong thing to republish.

* fix(cli): harden review run against EPIPE, target injection, and drift (#7983)

- Use writeStderrLineSafe in the timeout and spawn-error handlers and guard
  the progress stream, so an EPIPE on stderr can no longer skip the child
  kill, hang the promise, or orphan the review.
- Reject a review target carrying whitespace or a leading dash before it is
  re-tokenized by the child CLI (e.g. `123 --comment` silently authorising
  posting).
- Constrain --approval-mode to the same choices as the top-level CLI.
- Capture the child's exit signal and surface it (OOM/SIGKILL vs spawn fail).
- Sync the top-level `qwen --help` review description with the command.
- Register `run` in the review.test.ts subcommand expectation and add tests
  for the timeout branch, the readComposed guard, and target rejection.

* fix(cli): kill process group on review run timeout, harden edge cases (#7983)

The CLI relaunches itself in a child process (for --max-old-space-size),
so child.kill() only reached the relaunch wrapper — the real review was
reparented to PID 1 and kept burning API calls. Spawn with detached:true
and kill the process group (-pid) so the timeout actually terminates the
review.

Also: clamp negative --timeout-minutes to a 1-minute floor, distinguish
a corrupt composed artifact from a missing one in human-readable output,
and add test coverage for the default (non-JSON) output path.

* fix(cli): use specific MockInstance type for process.kill spy (#7983)

* fix(cli): capture review run verdict before cleanup, forward signals (#7983)

* fix(cli): reject quoted review targets, pin signal forwarding (#7983)

* fix(cli): keep captured review verdict when timeout fires after compose (#7983)

---------

Co-authored-by: verify <verify@local>
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>
2026-07-30 02:40:10 +00:00
易良
2abfa3d54e
fix(core): preserve active Todo context across tool turns (#7919)
* fix(core): preserve active Todo context across tool turns

* test(cli): update automatic turn prompt expectation

* fix(core): preserve Todo ownership across automatic turns

* fix(core): preserve Todo ownership at prompt boundaries

* test(todo): cover automatic reminder boundaries

* fix(core): throttle active Todo reminder re-injection to bound history growth

Every injected reminder copy lands permanently in chat history, so per-turn
injection grew the live context linearly with tool turns. Tool-turn injection
now re-issues the reminder only every third tool turn since the state was
last presented; turn-start injections always fire and reset the cadence. The
payload becomes a compact status/content line list capped at 800 characters.
History stays append-only, so provider prefix caching is unaffected.

Also: cover the new-ordinary-prompt-clears-stale-reminders invariant on the
real Config, add TUI coverage for the work-chain notification batch split,
cover todoWorkChainId continuation forwarding, and document the deliberate
enterWith binding in the daemon tool runner.

* fix(core): keep todo reminder before drained input

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-30 01:33:40 +00:00
zambalee
eb28b3038e
fix(core): guard against CJK-driven char/4 under-count in output clamp (#7963)
estimatePromptTokens's steady-state branch uses char/4 to estimate the
size of newly-added content (e.g. tool results just appended to history).
char/4 is a flat ratio that does not account for CJK text density, and two
independent real production failures (both 2026-07-28, on
KAT-Coder-V2.5-Dev and Nex-N2-mini) traced back to this: large CJK-dense
tool results (parallel read_file calls returning design docs) were
under-counted by 39-54%, so clampOutputTokensToWindow computed a
maxOutputTokens that was too large, and prompt + max_tokens exceeded the
window by ~1 token in production, triggering a 400 that propagates past
reactive compression (which correctly detects it has no room left, but
can't undo an already-oversized request).

Add an opt-in `conservative` parameter to estimatePromptTokens that
inflates only the new-content term by 1.5x (ceil'd) — calibrated against
the two observed under-count ratios with headroom, applied only at the
two call sites that feed clampOutputTokensToWindow (where under-counting
is dangerous), left off at the cheap-gate/compaction-trigger call sites
(where the existing code comments already document under-counting as
safe — it only makes compaction trigger earlier, never skip it).

Co-authored-by: Zamba Lee <zamba@thinktop.com>
2026-07-30 01:28:48 +00:00
易良
3d5924bd2f
fix(github-channel): validate and document reasonFilter (#8035)
* feat(github-channel): add reasonFilter config to skip unwanted notification reasons

Adds an optional `reasonFilter` allowlist to the GitHub channel config.
When set, notifications whose `reason` is not in the list are skipped
before any lane dispatch, reducing unnecessary API calls and agent work
for notification types the operator does not care about.

- New `reasonFilter?: string[]` field on `GithubConfig`
- O(1) Set lookup (`reasonFilterSet`); undefined = no filter (all reasons)
- Early-skip in the poll loop, before subject URL extraction and lane dispatch
- Two tests: filtered reasons skipped, unset filter processes all

Default behavior is unchanged (undefined = process all reasons).

* fix(channels): log github reason filter skips

* fix(channels): validate github reason filter

* fix(channels): address github reason filter comments

* fix(github-channel): reject invalid reason filters

* fix(github-channel): validate reason filters on connect

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 01:17:44 +00:00
ComplexSimply
d2ab8a5597
fix(core): allow transport stream retry during the thinking-only phase (#7938)
The transport stream retry was gated on !streamYieldedChunk, which any
thinking/reasoning chunk trips within seconds for thinking models. A
gateway closing the long-lived SSE connection minutes into the thinking
phase (UND_ERR_SOCKET) then propagated as a fatal error even though
zero user-visible content had been produced — making large generations
in YOLO/headless mode effectively impossible (#7832).

Track a separate streamYieldedContentChunk that is only set when a
chunk carries a non-thought candidate part (text, functionCall, …) and
gate the replay on it instead. Thought parts are ephemeral — never
recorded as the assistant's response in history — so replaying after
thinking-only output cannot duplicate visible content. Content-phase
failures keep the existing behavior: once a non-thought part has
flowed, the retry stays blocked and the error propagates.

The not-taken log now reports skipped_after_content, and the retry log
gains yieldedNonContentChunks so thinking-phase replays are visible in
diagnostics.

Fixes #7832

Co-authored-by: ComplexSimply <rudy.arrowsong@gmail.com>
2026-07-30 01:08:33 +00:00
jinye
1643a6c9ae
test(integration): Measure immediate prompt dispatch stages (#7994)
* test(integration): measure immediate prompt dispatch stages

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

* test(integration): cover update-level replay guard in user-echo matching (#7994)

* test(integration): exercise first-wins user echo latch in benchmark tracker (#7994)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-29 23:37:46 +00:00
qwen-code-dev-bot
6097d7ab63
ci: auto-minimize comments from org-blocked users (#7899)
* ci: auto-minimize comments from org-blocked users

Adds a scheduled workflow that runs every hour to scan recent
issue/PR comments and minimize any from users blocked at the org
level. This cleans up spam comments that were posted before a
block was applied.

The workflow:
1. Fetches the org's blocked-user list via REST API
2. Queries recent comments (last 2h) via GraphQL
3. Matches comment authors against the blocked list
4. Minimizes unmatched comments as OFF_TOPIC via GraphQL

Also triggerable manually via workflow_dispatch with a configurable
lookback window.

* ci: use repo blocklist file instead of org blocked-users API

The org blocked-users API requires admin:org scope which the CI
bot PAT doesn't have. Switch to a plain-text blocklist file at
.github/spam-blocklist.txt — one username per line, case-insensitive,
# for comments. No special scopes needed.

Also adds danialzivehdadr as the first entry.

* ci: make auto-minimize-spam failures visible, handle empty blocklist

* fix(ci): address review feedback on auto-minimize-spam workflow (#7899)

- Change hours input type from string to number (project convention)
- Extract duplicated step-summary writes into write_summary() function
- Capture gh stderr (2>&1) and include it in failure warnings
- Document coverage limits in header comment

* fix(ci): quote auto-minimize-spam expressions to pass yamllint (#7899)

* test(ci): add regression guards for auto-minimize-spam workflow invariants (#7899)

---------

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-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-29 23:37:42 +00:00
jinye
58eb07117b
fix(serve): Add certified session writer handoff (#7976)
* fix(core): add certified session writer handoff

Seal managed writer ownership after a durable recorder drain and allow trusted replacements to take over only when the transcript proof still matches.

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

* fix(core): retain writer claim after failed rollback

Keep the fixed transition claim unless the exact predecessor primary is restored, and avoid rollback after claim ownership changes or unlink completes with a reported error.

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

* fix(core): close session handoff claim races

Wait for claim-aware primary candidates to back out of transition gaps, and release losing claims when no primary transition started.

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

* fix(core): bound session handoff candidate waits

Fail closed when a claim-aware primary candidate is abandoned during a handoff transition, preserving the transition fence for authoritative recovery.

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

* fix(core): reconcile session handoff proof edges

Reject dangling transcript paths and reconcile ambiguous lock-record link outcomes by exact bytes before continuing a certified handoff.

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

* fix(core): preserve inspect error cause and document handoff fail-closed paths (#7976)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-29 23:36:58 +00:00
pratik wayase
563e51540c
test(web-shell): expand restored-history pagination regression coverage (#7907) 2026-07-29 23:35:13 +00:00