Commit graph

7794 commits

Author SHA1 Message Date
chinesepowered
5bb53eb645
fix(core): stringify const-derived enums in toOpenAPI30 (#7547)
toOpenAPI30 maps const to a single-value enum, then separately stringifies
enums because — per its own comment — Gemini strictly requires enums to be
strings. The two never met: the stringification keys off source['enum'],
which a const-only schema never sets, so the const value went through raw.

  { const: 5 }     -> { enum: [5] }        // number, breaks the rule
  { const: true }  -> { enum: [true] }     // boolean, likewise
  { enum: [1, 2] } -> { enum: ['1', '2'] } // the intended behavior

Build the const-derived enum with String() so both paths produce the same
kind of value.

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-25 09:25:15 +00:00
chinesepowered
3a85d48e77
fix(desktop): scale formatBytes past GB so terabyte sizes don't render as "undefined" (#7623)
formatBytes only had B/KB/MB/GB units and indexed the array with an
unclamped log-based exponent, so any value >= 1 TB overflowed to
"1.0 undefined". This is reachable in api-tools: the "Response too
large" guard formats an untrusted remote Content-Length, which a
server can report in the terabytes.

Extend the unit table through EB, clamp the exponent to the last unit,
and guard non-finite / sub-1 inputs so the function never emits
"undefined" or "NaN". Add formatBytes coverage to the existing
binary-detection test (range/limit guards plus the TB/PB/EB and
non-finite cases that fail on the old code).
2026-07-25 09:21:15 +00:00
destire-mio
dbadf49c6c
feat(stats): show generation timing metrics (#7677)
Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
2026-07-25 09:06:33 +00:00
Dragon
16ead53675
refactor(core): assemble the system prompt through one layered builder (#7707)
* perf(core): keep the volatile auto-memory section last in the system prompt

The managed auto-memory section (instructions + MEMORY.md indexes) is
rewritten in-session on every memory save, but it was pre-concatenated
into the middle of the userMemory blob — ahead of appendSystemPrompt and
git status, which are stable for the whole session. Every save therefore
invalidated the prompt-cache prefix from the middle of the system prompt,
and the stable/context/volatile layers were indistinguishable in code.

Store the auto-memory section separately on Config (getAutoMemoryPrompt)
and have every assembly site append it after all stable and context
content, yielding a stable -> context -> volatile layout: base prompt,
QWEN.md hierarchy + rules, append prompt, git status, auto-memory. Also
count the section in /context (previously dropped by the marker parser)
and keep /memory show and the context-size warning covering both layers.

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

* refactor(core): reuse buildSystemPromptSuffix for subagent auto-memory + add regression tests

- agent-core.ts: replace the inline auto-memory separator with
  buildSystemPromptSuffix so the '---' separator and trimming stay in
  sync with the other assembly sites (client.ts x2, ArenaManager.ts).
- client.test.ts: add coverage that a non-empty getAutoMemoryPrompt is
  appended as the last block of the main-session system instruction.
- contextCommand.test.ts: add coverage that a non-empty
  getAutoMemoryPrompt surfaces an 'auto memory' row in the /context
  memory breakdown.

* refactor(core): assemble the system prompt through one layered builder

Follow-up to the auto-memory reordering: the stable -> context -> volatile
order existed only as a convention spread across four call sites (client,
subagents, Arena, custom-instruction path), so a new segment could silently
be appended in the wrong position. Introduce assembleSystemPrompt with one
named slot per segment (base, contextFiles, appendPrompt, gitStatus,
autoMemory); it is now the single place that knows the order, and
getCoreSystemPrompt / getCustomSystemPrompt delegate to it so there is one
join implementation. buildSystemPromptSuffix returns to module-private.

Pure refactor: every assembly site produces byte-identical output, so no
prompt caches are invalidated by this change.

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

* fix(core): stop duplicating the auto-memory section in Arena workers

ArenaManager pre-appended buildSystemPromptSuffix(getAutoMemoryPrompt())
onto the in-process worker's systemPrompt, but AgentCore.buildChatSystemPrompt
already appends the auto-memory section itself when the worker builds its
system instruction (and the per-agent Config inherits a non-empty
getAutoMemoryPrompt() from the base via Object.create). The section therefore
appeared twice in the worker's prompt.

Drop the ArenaManager append so the volatile auto-memory layer is added exactly
once, by AgentCore, keeping it last. Add regression tests: ArenaManager no
longer embeds the auto-memory marker, and generateContent's per-call
systemInstruction branch still appends it.

* refactor(core): make buildSystemPromptSuffix module-private

It has no external importers after routing every assembly site through
assembleSystemPrompt, so dropping the export prevents a future caller
from bypassing the enforced layer order — matching the PR's stated intent.

* chore: drop unrelated lightningcss lockfile churn

Restore the "peer": true entries on the lightningcss optional deps that
an incidental npm install had removed; keeps this refactor's lockfile
diff empty so it does not mislead bisects on lightningcss resolution.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 09:03:56 +00:00
yijie zhao
0b5116a1bb
feat(core): configure stream rate-limit retry delays (#7674) 2026-07-25 08:56:21 +00:00
jinye
c4859627a7
feat(serve): Hot-reload workspace trust changes (#7268)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(serve): hot-reload workspace trust changes

Rebuild workspace runtime generations when trust policy changes, fail closed across daemon routes, and expose reconciliation status to SDK and Web Shell clients.

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

* codex: address PR review feedback (#7268)

Document the trust hot-reload capability and reuse the daemon environment fallback so the serve process environment guard remains satisfied.

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

* fix(cli): cache workspace trust status snapshots

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

* fix: address trust reload race regressions

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

* fix(serve): avoid repeated runtime containment

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

* fix(serve): harden workspace generation boundaries

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

* fix(serve): restore stale session owner fallback

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

* fix(serve): preserve workspace metadata across trust reloads

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

* fix(serve): align hot-reload trust semantics

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

* fix(serve): stop git-state watcher on dispose only, fix git chip test (#7268)

beginDrain stopped the git-state watcher but cancelDrain had no way to
restart it, leaving the watcher disposed until the next lazy poll.
disposeRuntime already stops git-state when the drain is committed, so
the beginDrain stop was redundant — remove it.

Also fix the WorkspaceSection git chip test that broke when the trigger
changed from <button> to <span role="button"> inside DropdownMenuTrigger:
use closest('[role="button"]') and interact with the dropdown menu item.

* fix(serve): address review feedback on trust polling and setValue assertion (#7268)

* fix(cli): correct daemon trust policy settings precedence and drain continuation (#7268)

* fix(serve): address review feedback on fork cleanup, persist simplification, sync guard, and a11y (#7268)

* fix(serve): assert before mutate in setValue, add pre-mutation guard, trust-before-generation ordering (#7268)

* fix(serve): honor system defaults in trust policy

Apply the documented settings precedence to daemon folder trust evaluation and keep workspaces outside configured trust rules fail-closed.

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

* fix(serve): preserve managed scratch trust during reloads

Keep daemon-created scratch workspaces trusted across policy reloads while retaining controlled-root validation, and reject trust mutations that cannot apply to these fixed-trust runtimes.

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

* fix(serve): guard auth provider persistence by generation

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

* refactor(serve): remove Web Shell trust UI

Keep this PR focused on daemon and SDK trust reconciliation; the Web Shell integration can follow separately.

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

* fix(sdk): restore workspace trust bundle budget

Preserve the merge-only browser bundle allowance required by the additive workspace trust v2 SDK surface after rebasing.

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

* fix(cli): handle trusted folder write failures

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

* fix(cli): keep capabilities available during trust reload

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

* fix(cli): address review feedback for workspace trust hot reload (#7268)

Drop the closed generation guard before retrying dynamic workspace
runtime creation so the retried runtime starts with a fresh, open guard
instead of inheriting the one closed during the abandoned attempt. Make
the /workspace/reload trust reconcile fire-and-forget with a swallowed
rejection (failures are reported separately), reuse sendGenerationClosedError
for the memory write error path, and assert the subagent deletion commit
boundary once before unlinking so a closed generation fails atomically.
Add coverage for the blocked-entry deep health probe and the /session/:id/cd
generation-close-during-flight path.

* fix(serve): close trust reload cleanup gaps

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

* fix(serve): use fire-and-forget for trust reconcile in workspace-qualified reload (#7268)

* fix(serve): address review feedback on generation guard and trust reconciler (#7268)

* fix(serve): use shared helpers for untrusted/generation-closed responses (#7268)

* fix(serve): continue cleanup after drain commit errors

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

* fix(cli): retry transient trust policy disappearance

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

* fix(serve): align status provider trust default with route-level check (#7268)

* fix(serve): clean up worktree on generation guard abort (#7268)

* fix(cli): guard tool and skill settings commits

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

* test(cli): add discriminating persistent-ENOENT test for trust policy read (#7268)

* fix(serve): close runtime generation gaps

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

* fix(serve): preserve scheduled task cap errors

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

* fix(serve): address review feedback on trust reconciler, settings guard, and route simplification (#7268)

* fix(serve): preserve containment retry semantics

Restore the last verified trust-reconciliation and generation-guard behavior after the automated review fix marked an unconfirmed disposal as contained and removed per-scope commit checks. Defer the remaining late-round suggestions to avoid expanding the PR.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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>
Co-authored-by: Qwen Autofix <qwen-autofix@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-25 08:43:07 +00:00
Jingyuan Tian
cb36659e39
fix(cli): align inline math recognition (#7701)
* fix(cli): align inline math recognition

* fix(cli): tighten inline code span handling

* test(cli): cover escaped inline math closer
2026-07-25 08:30:33 +00:00
Shaojin Wen
b9db14cb13
feat(review): enforce the submit-only write contract with a cleanup tripwire (#7691)
* feat(review): enforce the submit-only write contract with a cleanup tripwire

The /review skill's posting ban named one API route — `gh api
.../pulls/<n>/reviews` — and a run that had lost the surrounding prose to
four context compressions walked around it: it decided its findings were
all duplicates, never called submit, and hand-posted a consolidated
summary with `gh pr comment`. No authorisation gate, no downgrade
semantics, no `posted` fact, no completion line; nothing downstream could
tell it had happened.

Three layers close this:

- SKILL.md Step 7 now bans every write path to the PR (`gh pr comment`,
  `gh pr review`, `gh issue comment`, mutating `gh api` calls, comment
  edits/deletes) and routes prose wrap-ups into the review body via
  compose-review; the overlap-drop branch and Step 9 restate it at the
  two decision points where runs improvised.
- fetch-pr stamps `fetchedAt` (and `host`) into the fetch report — the
  review window's opening time, on the carrier that exists on every PR
  run.
- cleanup audits that window before sweeping: issue comments by the
  reviewing account are flagged with warning lines the skill must relay
  (submit posts reviews, never issue comments, so the overlap with
  sanctioned output is zero). Best-effort by design — offline or
  unauthenticated runs skip it silently, and an audit failure never
  fails the cleanup.

Validated against the real bypass: with a window opening at the original
run's fetch time, the audit names the exact hand-posted comment.

* fix(review): bypass-audit review follow-ups — shared-account filter, edit class, named skips

Addresses the review on the tripwire design:

- Medium: in CI the reviewing account is the bot that precheck/triage
  also post from, so their marker-stamped comments (<!-- qwen-… -->) are
  filtered out and the warning prose gains the third reading (another
  workflow under the same account). Without this, every mid-review push
  produced a false accusation the skill is told to relay verbatim.
- Low-Medium: producer contract tests — fetch-pr's report is now pinned
  to carry fetchedAt (a real timestamp) and --host, since dropping either
  silently turns the audit off with output identical to a clean window.
- Low: every skip path names itself on stderr (note: bypass audit
  skipped (…)), so the tripwire's off state is distinguishable from its
  all-clear state.
- Low: pre-window comments edited inside the window are flagged as an
  edit class of their own — ?since= filters on updated_at, so the rows
  were already fetched. Verified empirically that reactions do not bump
  an issue comment's updated_at before adding this.
- Low: the empty-window early return skips the currentUser() round trip;
  report-derived prNumber is cross-checked against the cleanup target and
  ownerRepo is shape-checked before either reaches a gh api path.
- Nits: use the tmpFile helper instead of re-deriving its path; SKILL.md's
  fetch-pr snippet now shows --host (the report records it and the audit
  queries it — a dropped host silently audits github.com).

* test(review): cover the github.com (host: null) path of the bypass audit

* fix(review): harden the bypass audit — review channel, boundary integrity, provenance

Addresses the second review round on the tripwire:

- Review-channel coverage: submit now records a receipt (the one review
  id it was authorised to create, parsed from the POST response), and
  cleanup flags any in-window review by the reviewing account the
  receipt does not vouch for — `gh pr review` and direct POSTs to
  pulls/<n>/reviews were bannable but invisible before. No receipt
  vouches for nothing (fail-safe).
- Boundary integrity across restarts: fetch-pr preserves the earliest
  window opening as auditSince when it overwrites its own report (the
  head-drift rule reruns it), so writes made during an abandoned attempt
  stay inside the audit window. A window from a different PR left at the
  same path is not inherited.
- Clock skew: the audit boundary backs off two minutes from the recorded
  opening — fetchedAt is local time compared against GitHub's server
  timestamps, and a fast local clock could otherwise hide the first
  moments of the review. Errs toward over-flagging.
- Marker provenance: the automation filter is anchored to the body
  START, so a hand-posted summary that merely quotes a marked bot
  comment (or hides the marker mid-body) stays visible to the tripwire.
- Named skip fidelity: ENOENT alone means "no fetch report" (any other
  read failure names its code); gh failures surface the first non-empty
  stderr line instead of the generic Command-failed wrapper.
- Tests: rogue-review flag + receipt exclusion, skew boundary, auditSince
  window, edited-warning rendering through runCleanup, malformed-report
  table, EACCES vs ENOENT, stderr extraction, marker-quoting visibility,
  and producer tests for auditSince preservation.

* fix(review): distinguish a corrupt prior fetch report from an absent one

The auditSince-preservation block swallowed every read/parse error as "no
previous report". A crash mid-write leaves truncated JSON: on the next
drift restart JSON.parse threw, the catch reset auditSince to the new
fetch, and a bypass write from the abandoned attempt escaped the audit
window silently. ENOENT is still the silent first-attempt path; a
non-ENOENT read failure and an unparseable existing report each warn on
stderr that the window may not reach an earlier attempt. Tests cover
corrupt-JSON, ENOENT-silent, and EACCES-named.

* fix(review): accumulate submit-receipt ids across a window, and test the producer

Two gaps in the review-channel bypass audit's receipt contract:

- The receipt was overwritten on every submit, but the audit window spans
  drift restarts (fetch-pr preserves auditSince), so two sanctioned
  submits could fall in one window — the single-id receipt then vouched
  only for the last, and cleanup flagged the earlier legitimate review as
  a bypass (a false positive for a write submit itself made). The receipt
  now accumulates ids (read prior, append, dedupe); cleanup reads the set
  and excludes all of them. Both sides migrate a legacy single `reviewId`.
- The producer half had no test: submit.test.ts's ghMock returns '', so
  JSON.parse(response) threw and the receipt write always hit its catch —
  the happy path never ran. Added producer tests (id/event/timestamp
  written, accumulation across two submits, legacy migration) driven from
  inside the fixture dir, plus a cleanup test that spares every id in a
  multi-id receipt.

* fix(review): correct SKILL.md prose-wrap-up guidance; de-fragilize fetch-pr mock reset

- SKILL.md told the orchestrator to route a free-form prose wrap-up
  through compose-review's input, but ComposeReviewInput has no free-text
  body field — the body is computed from structured state, by design (the
  model does not author PR-facing prose). Following that instruction would
  force the model to misuse a structured field or forge a `body` submit
  refuses. Both passages now say the recap belongs in the TERMINAL
  summary; the PR receives only submit's computed body plus inline
  comments. This aligns the guidance with the submit-only principle rather
  than contradicting it.
- fetch-pr.test.ts's producer beforeEach re-set git/gh but not
  readFileSync, and clearAllMocks does not reset implementations — so a
  mockReturnValue from one test could leak into a later test relying on
  the default. The beforeEach now re-asserts the ENOENT default, removing
  the ordering dependency.

The receipt-overwrite and receipt-untested suggestions from the same
review were already addressed in 87554647c (this review ran against the
prior commit).

* fix(review): address maintainer review — host restore, shared receipt parser, auditSince bound

Three findings from @yiliang114:

- cleanup's audit set the gh host for the PR but never restored it —
  harmless today (cleanup runs last) but a leak if any code runs after.
  It now saves the prior host (new getGhHost) and restores it in a
  finally, so the Enterprise override is scoped to the audit block.
- submit's readReceiptIds and cleanup's readSubmitReceipt duplicated the
  receipt-shape parse (reviewIds[] + legacy reviewId migration). Extracted
  to lib/receipt.ts parseReceiptIds so a schema change is one edit both
  inherit; the two call sites now differ only in their empty wrapper
  ([] vs Set). Unit-tested directly.
- fetch-pr's auditSince adoption: documented that `prevSince < auditSince`
  (auditSince = fetchedAt = now) is also the upper bound — a corrupted
  far-future timestamp is rejected because the window only ever moves
  backward, so it cannot push the audit ahead of every real comment.

Tests: host-restore assertion in cleanup, parseReceiptIds table.

* test(review): exercise the auditSince-preference branch in the producer test

The drift-restart producer test seeded only fetchedAt, leaving the
auditSince-over-fetchedAt branch (the third-restart case, where a prior
report already carries an earlier auditSince) untested. Added a case
seeding both. Reported by @yiliang114.

* fix(review): parseReceiptIds must not throw on valid non-object JSON

JSON.parse('null') (or a bare number/string/array) succeeds, and the
next field access threw a TypeError — breaking the documented never-throws
contract. Both callers wrap it in try/catch today, but the contract
invites a future caller to omit the guard. Now guards value === null ||
typeof value !== 'object' and returns []. Tests cover null/number/string/array.

* feat(review): detect head drift at presubmit and cap the verdict (#7692)

* feat(review): detect head drift at presubmit and cap the verdict

A PR that advances while /review runs carries commits no agent read, and
an Approve issued past them certifies unreviewed code. Dogfooded on a
live PR whose head moved four times in one day: the only run that
noticed did so by accident (a context compression triggered a re-fetch),
and the 422-recovery path — the one place drift was handled — only fires
after a submission already failed.

presubmit now fetches the live head alongside the author (one jq
projection), compares it to the reviewed fetchedSha, and on drift adds a
headDrift report field plus a downgrade reason through the existing
machinery — an Approve is capped to COMMENT automatically, naming both
SHAs. A best-effort compare call annotates the delta (ahead_by, touched
files, diverged = force-push); the drift verdict never depends on it.

SKILL.md turns the report into one deterministic size call: if the
unreviewed commits touched files the findings anchor to — or the compare
is unavailable or diverged — apply the 422-recovery rule proactively and
restart at the new SHA; otherwise submit against the reviewed SHA with
the downgrade sentence saying so. Restarts cannot loop: they only happen
when the new commits touched the very files under review, which is
exactly when re-reading them is the review rather than overhead.

* fix(review): head-drift review follow-ups — compute anchorsAtRisk fail-safe in code

Addresses the review at be2cbb91:

- Blocker: the 50-file cap turned SKILL.md's intersection test into a
  silent fail-open — on a real 283-file base-merge drift the surviving
  alphabetically-first paths contained no packages/cli or packages/core
  file, so the gate read "safe" on exactly the largest drifts. The
  decision is now computed in presubmit as headDrift.anchorsAtRisk,
  fail-safe on every hole a hand intersection falls into: local
  truncation, the compare API's own 300-file ceiling, diverged history,
  an unavailable compare, or a missing findings list. filesTotal carries
  the real count and the downgrade reason reports it instead of the cap.
- Medium: restarts are bounded — at most one per review; a restarted run
  that reaches presubmit drifted again submits at its reviewed SHA with
  the drift named. The Approve cap holds either way.
- Low: a deleted PR author (author: null) no longer kills presubmit —
  the projection parse is fail-soft, restoring the bare-string
  behaviour the jq change had dropped.
- Low: an abbreviated commit_sha no longer reports false drift — an
  identical/zero-ahead compare is the endpoint's own proof that nothing
  is unreviewed, and it outranks a SHA-string mismatch.
- Tests: truncation and the API-cap fail-safe pinned; exactly-one-gh-call
  pinned on the happy path; a throwing compare pinned to keep the drift
  verdict; the --host fixture back to a neutral author; the
  paren-absence assertion replaced with the exact message.

* fix(review): head-drift round 2 — rename paths, metadata fail-closed, shared restart bound

Addresses the follow-up review:

- Renamed files: the compare projection now keeps previous_filename
  alongside filename, so an unreviewed commit that renamed a finding's
  anchor file (under its old path) still intersects and marks
  anchorsAtRisk. filesTotal dedupes the union.
- PR-metadata read failure now fails CLOSED: a thrown pulls fetch
  (transport/auth/404 on that endpoint) leaves the head unknown, so
  neither self-PR nor drift can be checked — the run emits a downgrade
  reason and caps the Approve rather than silently proceeding as if it
  had verified. A successful author:null response stays fail-soft (that
  case is answerable — not a self-PR).
- The one-restart-per-review bound now explicitly covers BOTH the
  proactive drift restart AND the reactive 422 recovery; the 422 branch
  no longer restarts unconditionally once the bound is spent.
- --new-findings must carry body-only Criticals' file paths too, so a
  drift touching an unmappable Critical's file is not read as safe.
- Tests: metadata-failure downgrade, rename-path intersection.

Not changed (design boundaries, reasoning in the PR thread): anchorsAtRisk
gates ANCHOR validity, not finding-truth (the Approve cap covers stale
findings; an inline comment on the reviewed SHA is acceptable and GitHub
marks it outdated); the presubmit→submit TOCTOU can only make the verdict
more conservative, never less.

* fix(review): validate the --new-findings file before trusting it as a drift safety proof

The findings list gates anchorsAtRisk — a disjoint intersection lets a
review submit past head drift — so a malformed file must not be able to
prove a false all-clear. parseFindingsFile now collapses to null (unknown
→ fail-safe at-risk) when the file will not parse, is not an array, or
holds an entry without a string path; a bad entry rejects the WHOLE file
rather than silently shortening the set (a dropped finding would read as
disjoint). A missing line normalizes to 0 (path is what the intersection
uses). Pure-validator table tests plus a handler test proving
anchorsAtRisk stays true on disjoint files when the findings are garbage.

* fix(review): surface a malformed --new-findings file instead of degrading silently

parseFindingsFile returning null made the drift path fail safe
(anchorsAtRisk: true) but ALSO silently emptied newFindingKeys, disabling
the existing-comment overlap check — so a run could not tell "no
overlaps" from "the dedup input was garbage" and might re-post comments a
prior run already made. presubmit now sets findingsFileInvalid on the
report and pushes a downgrade reason (and caps the Approve) when a
--new-findings path was given but did not parse, so the double-directional
degradation is visible and the skill knows to regenerate the file. Tests:
flag + downgrade with no drift, and no-flag on a valid/absent file.

* fix(review): treat a `behind` force-push-to-earlier as drift; render capped totals as a lower bound

Two review suggestions:

- classifyHeadDrift proved-same any non-diverged compare with aheadBy 0,
  which wrongly included `behind` — a head force-pushed BACK to an earlier
  commit. The reviewed SHA is then ahead of the head and off the PR's
  line, so its anchors may not exist in the head at all: that is drift.
  provedSame is now `status === 'identical'` only; `behind` drifts, forces
  anchorsAtRisk, and gets its own reason ("PR head moved to an earlier
  commit") instead of the self-contradictory "+0 unreviewed commit(s)".
- The public downgrade reason printed the exact filesTotal even at
  GitHub's 300-file compare cap, where the count may be an undercount. It
  now renders "300+ file(s)" at the cap.

---------

Co-authored-by: verify <verify@local>

---------

Co-authored-by: verify <verify@local>
2026-07-25 08:27:41 +00:00
Shaojin Wen
a470ba626c
feat(review): add comment-status helper for existing-thread triage (#7690)
* feat(review): add comment-status helper for existing-thread triage

One deterministic pass over a PR's existing inline comments, replacing
the per-comment `gh api` fetches the orchestrating model used to make
during /review: anchor validity at the live head (outdated detection,
with a file-level exemption), whether the anchored file changed in the
reviewed worktree since each comment's commit and which commits touched
it (the re-check's candidate "fixed by" list), reply participation and
PR-author response, the blocker signal (same carriesBlockerSignal as
pr-context, so the two surfaces agree by construction), and
worktree-vs-live head drift.

Measured on a heavily discussed PR (72+ inline comments), a single
review run burned 20+ model turns re-deriving exactly these fields one
comment id at a time. SKILL.md now runs the subcommand in Step 1 and
routes the Step 6 re-check's status questions at the report; comment
bodies stay in the pr-context file under its untrusted-data preamble,
and a comment-status failure only warns — it is an index, not the
evidence, so it never sets the context-unavailable state.

* test(review): add comment-status to the subcommand registry expectations

* fix(review): comment-status review follow-ups — size warning, --host wiring, scope clauses

Addresses the review at d098e4feb:

- High: warn when the report exceeds read_file's truncation threshold,
  mirroring pr-context — measured 53k chars on the benchmark PR, where a
  single read lost 36 of 71 threads (24 blocker-flagged) and the cut JSON
  did not parse. The warning points at jq first: the file is
  machine-shaped in a way the Markdown context file is not.
- Medium: thread --host through — the SKILL.md command block now says to
  pass it (each subcommand is its own process, so a host set elsewhere
  cannot carry over), and comment-status joins the host-required lists in
  SKILL.md and the code-review docs.
- Minor: Step 6's routing sentence now states both scope limits — the
  report exists only when Step 1 wrote it (worktree mode), and it indexes
  inline threads only; issue-/review-level blockers keep the context-file
  walk.
- Minor: touchedByTotal exposes the real commit count behind the capped
  touchedBy list, so a cut list is visible instead of reading as "the fix
  is not among them".
- Nits: drop the never-read `side` field; guard authorReplied against a
  deleted-author/deleted-replier '' === '' match; correct the force-push
  doc comment (a shared object database usually retains the old commit,
  so the range widens — fail-safe — rather than going unknown).

* fix(review): comment-status second-round follow-ups — CWD-safe pathspec, shared walk, stale flag

Addresses the LGTM-with-suggestions round:

- The git probe's pathspec is anchored with `:(top)`: run from a
  subdirectory of the worktree, the old CWD-relative form returned empty
  output with exit 0 and every thread read as "untouched since the
  comment" — silent, and pointing the one direction this index must not
  fail in. A real-git integration test now drives the probe from the
  repo root AND a subdirectory (plus the cap, the memo, and the
  missing-commit gate — none of which the injected-probe unit tests
  could see).
- findRootId is imported from pr-context (made generic and exported)
  instead of duplicated — the thread walk now agrees by construction,
  like the blocker signal already did.
- Head drift is denormalized onto every thread as code.staleWorktree, so
  a jq consumer of threads[] cannot skip the top-level flag by
  construction.
- Commit existence is memoized per SHA (it never depended on the path),
  halving git spawns on thread-heavy PRs; summarizeThreads gets a named
  return interface like every other exported shape here.
- DESIGN.md gains the missing section: why comment-status is a separate
  subcommand and why the second fetch of pulls/{n}/comments is
  deliberate (process boundary — pr-context must stay pure-API for
  lightweight mode; this one exists to join API facts with worktree git).

* fix(review): harden comment-status against untrusted-PR inputs

Addresses the security review round:

- Symlink --out: the command now runs from the trusted main checkout (so
  a relative --out cannot be redirected through a symlink an untrusted PR
  planted in its own worktree) and scopes its git queries to the worktree
  with `git -C <worktreePath>`, which it locates itself. SKILL.md no
  longer cd's into the worktree for it. The report lands in the main
  checkout's .qwen/tmp alongside every sibling report.
- Non-ancestor comment commit: after a force-push the anchor commit can
  survive in the shared object store without being on HEAD's history, so
  `sinceSha..HEAD` is empty and a changed file reads as changed:false —
  the one direction this index must not fail in. A single
  `merge-base --is-ancestor` gate now returns 'unknown' for a
  non-ancestor OR a missing commit, replacing the cat-file existence
  check (one git process instead of two).
- Literal pathspec: the GitHub-supplied path is passed as
  `:(top,literal)<path>` so a value like `:(exclude)a.ts` cannot be read
  as pathspec magic and inspect unrelated files.
- Fetch-race drift: the live head is sampled before AND after the
  comments fetch; a push landing mid-fetch (which would pair newer
  anchor mappings with a stale comparison) is now detected, recorded as
  both samples, and warned on distinctly from ordinary worktree lag.
- pr-context renders the root comment id in the Open and Already-discussed
  sections, giving Step 6 a stable join key back to comment-status's
  per-thread rootId (the blocker renderer already did this).
- Handler-level tests (mocked gh/git/fs) for the three drift outcomes,
  plus real-git integration cases for the non-ancestor gate and the
  literal pathspec. Multi-page pagination is a non-issue: gh api
  --paginate merges top-level arrays into one (verified on the live
  93-comment PR).

* fix(review): comment-status round 3 — precise staleWorktree, worktree-missing warning, discriminating pathspec test

Addresses three Suggestions:

- staleWorktree is now keyed on worktreeStale alone, not the headDrift
  union. A head that merely moved between the two samples while the
  worktree already matches the final head is NOT a superseded checkout,
  so its threads no longer carry staleWorktree:true against the field's
  documented meaning. headMovedDuringFetch stays a separate top-level
  flag + warning.
- A missing worktree (comment-status run before fetch-pr or after
  cleanup) now sets worktreeMissing on the report and prints a warning —
  previously every thread degraded to code:'unknown' silently, readable
  as "nothing changed".
- The literal-pathspec test now uses a discriminating pathspec
  (`:(glob)pkg/**`): magic would match the changed file (true), literal
  is a nonexistent filename (false), so asserting false actually fails if
  the `:(top,literal)` prefix is dropped — the old `:(exclude)…` read
  false under both interpretations. A plain-path control proves the probe
  is live.

* test(review): make the comment-status negation test actually exercise negation

The body 'No blockers here' matched no BLOCKER_PATTERN (the bare plural
never triggers /\bblocking\b/ etc.), so isBlocker returned false before
the negation branch ran — false coverage. It now uses 'No blocking
issues', which matches the signal and must be suppressed by the leading
'No', plus an un-negated control that asserts true.

* test(review): assert per-thread staleWorktree and --host wiring; document ghApiAll merge contract

Two test gaps + one recurring-review clarification:

- The worktree-lag drift test now includes a thread and asserts
  staleWorktree:true is denormalized onto its code object — the old
  positive case had zero threads, so the denormalization loop iterated
  over nothing and would pass even if the block were deleted.
- A --host test now asserts setGhHost is called with the argv host,
  matching the presubmit analog; without it a dropped setGhHost would
  silently target github.com for a GHE review.
- ghApiAll's doc now explains why one JSON.parse is correct on multi-page
  output: gh --paginate MERGES top-level arrays into one (it does not emit
  one array per page); the per-page-concat failure only affects
  key-nested arrays, which is exactly why ghApiAllNested exists. Verified
  on a 4-page 97-comment response.

* fix(review): comment-status degrades gracefully on failure per its SKILL.md contract

SKILL.md promises this command is an index, not evidence — "if it fails
(auth, network), warn and continue" — but runCommentStatus had no
try/catch, so an ensureAuthenticated() or gh throw propagated as an
unhandled rejection and killed the whole review. The runtime body is now
wrapped: any throw writes a minimal empty report ({prNumber, ownerRepo,
error, threads: []}) so downstream jq still parses, prints a
"comment-status failed" warning, and exits 0. Handler test pins the
auth-failure path (no throw, empty report, warning). Reported by
@yiliang114.

* test(review): pin comment-status owner_repo guard and truncation-size warning

Two untested paths flagged in review: the owner_repo-without-slash guard
(a caller error that must still throw, distinct from the runtime
graceful-degradation path) and the report-size warning that fires when
the JSON crosses read_file's truncation threshold.

* fix(review): comment-status degraded report carries the full shape, not a stripped one

The graceful-degradation path wrote { prNumber, ownerRepo, error,
threads: [] }, omitting headDrift/summary/headMovedDuringFetch/etc. A
consumer reading report.headDrift then got undefined (falsy = "no
drift"), silently mistaking a total index failure for a clean "nothing
moved" — and the review orchestrator keys code-fact warnings on exactly
that field. The degraded report now emits the same shape as the success
report with safe defaults plus `error`, so a consumer that checks `error`
sees the failure and one that reads a fact gets a neutral value, never a
misleading one. Reported by @doudouOUC.

* fix(review): degraded report's worktreeMissing must not contradict worktreeHeadSha: null

The catch-block report hardcoded worktreeMissing: false beside
worktreeHeadSha: null — a positive "worktree present" assertion the
success path (worktreeMissing = worktreeHeadSha === null) would never
make for a null head. A consumer reading worktreeMissing without gating
on error would conclude the worktree exists on a run where nothing is
known. Now true, matching the null head and the fail-safe reading (code
facts unavailable). Reported by qwen-code-ci-bot.

---------

Co-authored-by: verify <verify@local>
2026-07-25 08:26:08 +00:00
jinye
8f667f5bdc
feat(integrations): add retrieval-only external context search (#7586)
* feat(integrations): add direct external context provider

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

* fix(integrations): harden external context failure handling

Preserve provider timeout classification, reject ambiguous Mem0 statuses, release rejected response bodies, and clarify credential and workspace deployment boundaries.

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

* refactor(integrations): narrow external context to retrieval

Limit Phase 1 to one provider-bound search tool, remove hooks and writes, and document the direct profile's actual permission and isolation boundaries.

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

* fix(integrations): harden external context deployment

Pin the managed MCP source through an administrator-owned command-line configuration, document the Direct Profile trust boundary, and remove unused logging/runtime abstractions.

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

* fix(integrations): preserve external context results

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

* fix(integrations): honor provider proxy settings

Install an environment-aware dispatcher before the external context MCP server starts so enterprise egress proxy and NO_PROXY settings apply to provider requests. Document the managed launcher environment and cover startup wiring.

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

* fix(integrations): diagnose invalid proxy settings

Classify proxy dispatcher construction failures as sanitized configuration errors so managed deployments can identify an invalid proxy environment without exposing proxy credentials.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-25 08:18:27 +00:00
Shaojin Wen
638dc9a1c3
fix(triage): resolve finalize PRs from the open-PR list, not the commit association (#7706)
Live verification of the finalize loop (#7693) on its first real fork PR
caught the deferred approval being silently dropped: CI landed green, the
approve-on-green marker was in place, but commits/:sha/pulls returned an
empty list for the PR's current head — the association endpoint is not
reliable for fork-branch commits (workflow_run.pull_requests is likewise
empty for forks). The run logged 'No open PR; nothing to finalize' and
exited, so the approval never posted.

Resolve PRs by filtering the open-PR list on head.sha as the primary
source — it cannot miss the PR a current-head firing belongs to — and keep
the association endpoint as the second source (when it works it also
surfaces PRs whose head moved past the SHA, which powers the stale note).
Union both, deduped.
2026-07-25 07:20:45 +00:00
Shaojin Wen
52f6eaf8f0
fix(triage): resolve stage comment ids by marker at patch time, harden model injection (#7703)
* fix(triage): resolve stage comment ids by marker at patch time, harden model injection

Two hardenings from shepherding #7693, plus a wording fix:

- Re-run comment updates now resolve the target comment id by its stage
  marker (bot-author-filtered, startswith match) immediately before each
  PATCH, instead of trusting remembered ids or list positions. Observed on
  a real re-run: the agent PATCHed the stage=3 comment with stage=1 content
  mid-run before self-correcting — with four bot comments in the thread,
  remembered-id bookkeeping is fragile.
- The model-name injection step previously no-opped silently if the
  'qwen3.7-max' literal ever left the skill (shipping the wrong signature
  in every comment), and corrupted the skill text on model names carrying
  sed metacharacters (/ & \). It now fails the job loudly when the target
  literal is missing and escapes the replacement. Covered by a behavioral
  test that runs the extracted step script against fixture files.
- The finalize status text said 'stage comments above', but the status
  comment is created first, so the stage comments are below it — now
  'in this thread'.

* chore(triage): drop unrelated formatting churn from qwen-triage.yml

The previous commit let prettier rewrite untouched lines (runs-on quoting,
comment spacing) while formatting the edited step. Restore those lines to
main's form; the diff now carries only the injection hardening and the
status wording fix.

* test(triage): pin the stage_comment_id recipe's load-bearing constraints

Guards the startswith match and the bot-author filter in the skill's re-run
comment-id recipe against silent regression — a contains match or a dropped
author filter re-introduces the wrong-comment-overwrite bug this PR fixes.

* test(triage): shim BSD sed only on darwin in the injection test

The extracted step script uses GNU 'sed -i' (the step only runs on ubuntu
runners), but this suite also runs in the macOS merge-queue job where BSD
sed needs an extension argument after -i. Rewrite to sed -i '' on darwin
only — on GNU sed a separated '' parses as the sed script, so the
unconditional rewrite would break the Linux runs that mirror production.
2026-07-25 07:20:01 +00:00
Shaojin Wen
d61b0ea475
perf(web-shell): paint the composer git chip before git status completes (#7680)
* perf(web-shell): paint the composer git chip before git status completes

New sessions gated the chip on a full `git status --porcelain` subprocess
behind GET /workspaces/:ws/git, so the branch chip appeared hundreds of
milliseconds (worst case seconds) after the composer was ready.

The daemon now keeps a per-workspace last-known summary with in-flight
dedup and a 2s background-refresh throttle: the default GET returns the
cached status (branch-only on a cold start) immediately and recomputes in
the background, publishing git_status_changed over SSE only on a delta,
while ?wait=1 keeps the previous blocking semantics. The composer fetches
both paths concurrently — the fresh GET also covers the no-session state,
which has no per-session SSE stream — so the branch paints in ~3ms and
the counters land when the computation finishes. The sidebar keeps
wait:true since it has no SSE fill-in path.

* fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680)

* fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680)

* fix(web-shell): add exhaustiveness guard and worktree git-status test (#7680)

* fix(cli): use writeStderrLineSafe in git-status refresh error path (#7680)

* fix(web-shell): add debug trail to fresh-path catch and test branch-watcher dispose guard (#7680)

* fix(cli): assert writeStderrLineSafe in git-status refresh failure test (#7680)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-25 07:05:52 +00:00
Dragon
de1c317724
perf(core): keep the volatile auto-memory section last in the system prompt (#7651)
* perf(core): keep the volatile auto-memory section last in the system prompt

The managed auto-memory section (instructions + MEMORY.md indexes) is
rewritten in-session on every memory save, but it was pre-concatenated
into the middle of the userMemory blob — ahead of appendSystemPrompt and
git status, which are stable for the whole session. Every save therefore
invalidated the prompt-cache prefix from the middle of the system prompt,
and the stable/context/volatile layers were indistinguishable in code.

Store the auto-memory section separately on Config (getAutoMemoryPrompt)
and have every assembly site append it after all stable and context
content, yielding a stable -> context -> volatile layout: base prompt,
QWEN.md hierarchy + rules, append prompt, git status, auto-memory. Also
count the section in /context (previously dropped by the marker parser)
and keep /memory show and the context-size warning covering both layers.

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

* refactor(core): reuse buildSystemPromptSuffix for subagent auto-memory + add regression tests

- agent-core.ts: replace the inline auto-memory separator with
  buildSystemPromptSuffix so the '---' separator and trimming stay in
  sync with the other assembly sites (client.ts x2, ArenaManager.ts).
- client.test.ts: add coverage that a non-empty getAutoMemoryPrompt is
  appended as the last block of the main-session system instruction.
- contextCommand.test.ts: add coverage that a non-empty
  getAutoMemoryPrompt surfaces an 'auto memory' row in the /context
  memory breakdown.

* fix(core): stop duplicating the auto-memory section in Arena workers

ArenaManager pre-appended buildSystemPromptSuffix(getAutoMemoryPrompt())
onto the in-process worker's systemPrompt, but AgentCore.buildChatSystemPrompt
already appends the auto-memory section itself when the worker builds its
system instruction (and the per-agent Config inherits a non-empty
getAutoMemoryPrompt() from the base via Object.create). The section therefore
appeared twice in the worker's prompt.

Drop the ArenaManager append so the volatile auto-memory layer is added exactly
once, by AgentCore, keeping it last. Add regression tests: ArenaManager no
longer embeds the auto-memory marker, and generateContent's per-call
systemInstruction branch still appends it.

* test(core,cli): cover auto-memory layering in headless and /memory paths

Address review feedback on #7651:
- add an agent-headless test asserting a non-empty getAutoMemoryPrompt is
  appended as the trailing volatile section of the subagent system prompt
- add useShowMemoryCommand tests covering the two-layer (context +
  auto-memory) concatenation: both non-empty (joined by the section
  separator), each layer alone (no separator), and both empty

* fix(cli): narrow getCombinedMemoryMessage return type to fix build

The Message union includes the COMPRESSION variant, which has no
content field, so getCombinedMemoryMessage(): Message | undefined
made combined!.content fail strict type-checking (TS2339), breaking
tsc --noEmit and the packages/cli build in CI.

Narrow the return type to the content-bearing Message member via
Extract<Message, { content: string }> and give find() a type-guard
predicate so the narrowed type flows to callers.

* fix(core): clarify context-size warning covers auto-memory too

The memory context-size warning is fed the combined size of the context
files (getUserMemory) and the auto-memory section (autoMemoryPrompt), but
its text only named "QWEN.md/context instructions". Broaden the message to
"always-on context (QWEN.md context files + auto-memory)" so users know the
auto-memory index also counts toward the estimate, and update the assertions.

* test(core): assert auto-memory prompt is cleared in safe mode

The safe-mode branch of refreshHierarchicalMemory clears
this.autoMemoryPrompt, but the safe-mode test only asserted
getUserMemory() and getGeminiMdFileCount(). Add an assertion that
getAutoMemoryPrompt() is empty so a future refactor dropping that
line cannot silently leak the auto-memory section into safe-mode
system prompts.

* fix(core): stop duplicating the auto-memory section in the remember agent

The managed remember agent's system prompt already embeds the full
auto-memory protocol and MEMORY.md indexes via buildCleanMemorySystemPrompt
(forceFullProtocol). After splitting auto-memory into a separate
Config.getAutoMemoryPrompt() field, AgentCore.buildChatSystemPrompt appends
it a second time on every managed remember run, duplicating the whole section
and, in clean mode, re-injecting parent-session memory into the intended
blank-slate agent.

Zero out getAutoMemoryPrompt() on the forked-agent config for all context
modes so the section is present exactly once (via the remember prompt), and
keep clearing getUserMemory() in clean mode. Add regression coverage for both
clean and non-clean modes.

* test(core): assert managed auto-memory prompt is standalone

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 06:30:39 +00:00
Shaojin Wen
cf6b8a0e6b
perf(cli): cache GitHub PR list in the daemon route with a 60s TTL (#7705)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-25 06:07:30 +00:00
Shaojin Wen
efdcb63936
fix(web-shell): add :focus-visible outline to GitHub PR list rows (#7704)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-25 04:10:44 +00:00
Gaurav
f4e333c580
fix(mcp): harden OAuth callback handling (#7510) 2026-07-25 03:56:27 +00:00
qwen-code-dev-bot
e560f8e41f
fix(cli): clear stale retry error when agent auto-recovers mid-turn (#7681)
* fix(cli): clear stale retry error when agent auto-recovers mid-turn

When a transient API error (e.g. UND_ERR_SOCKET: other side closed)
occurs during a tool-use loop, handleErrorEvent sets a static
pendingRetryErrorItem with a "Press Ctrl+Y to retry" hint. If the
stream processor auto-retries the failed call and the agent completes
the turn successfully, the error message lingers on screen because
the turn-completion path only clears countdown-based errors, not
static ones.

Additionally, lastPromptErroredRef stays true, causing the turn to
be reported as failed (onDeliveryFailed) even though it succeeded.

Fix: in the turn-completion success path, if a static retry error
is still showing (no active countdown timer), clear it and reset
the errored flag. This correctly handles the auto-recovery case
without affecting the existing behavior where errors that truly
end the turn keep their retry hint visible.

* fix(cli): gate stale-error clearing on non-terminal turn (#7681)

The previous else-if branch cleared pendingRetryErrorItem whenever it
was set and no countdown timer was active — including after terminal
Error events. Since handleErrorEvent always clears the timer before
setting the error item, every fatal API error hit this branch,
silently swallowing the error and reporting the turn as delivered.

Gate on !lastPromptErroredRef.current so only countdown-originated
errors (from successful auto-retry) are cleared; terminal errors
remain visible with the Ctrl+Y hint.

Add regression tests for both paths.

* fix(cli): clear stale countdown item on auto-recovery (#7681)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-07-25 02:45:26 +00:00
Shaojin Wen
a4f5e50d19
feat(web-shell): add read-only GitHub pull requests panel (#7683)
* feat(web-shell): add read-only GitHub pull requests panel

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

* fix(web-shell): address review findings for GitHub PRs panel

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

* fix(web-shell): clamp GitDialog view when PR capability is withdrawn mid-session

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

* fix(web-shell): address review feedback for GitHub PRs panel (#7683)

- Add workspace_github_prs to integration test baseline capabilities
- Sanitize git root path in error responses to prevent path leakage
  when workspace is a repo subdirectory
- Add NEUTRAL check-run conclusion test case
- Add not.toContain path-leak assertion for sanitization test
- Add pending checks indicator UI test
- Add timeAgo utility unit test

* fix(web-shell): address review feedback for GitHub PRs panel (#7683)

- Sanitize workspace paths before truncating the error message so a path
  straddling the 512-char display boundary is redacted, not cut mid-token
- Render a badge for the review_required decision instead of leaving it dead
- Align PR row icon sizes (12px) with sibling git dialogs

---------

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-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-25 02:44:50 +00:00
Nothing Chan
331d58c652
fix(core): allow reading saved plan files without a confirmation prompt (#7678)
The default plans dir (~/.qwen/plans) sits outside the workspace and was
in none of ReadFileTool's permission-free roots, so reading a saved plan
back landed on an ask confirmation — popped at exactly the moment the
user approved the plan and told the agent to start coding, and resolvable
as a denial in non-interactive/ACP flows. With the approved-plan pointer
(#7197) the saved file is the model's only recovery route for the plan
text, so the read must not stall on a prompt.

Adds config.getPlansDir() to ReadFileTool.getDefaultPermission's
allowedRoots and to AcpAgent.buildAcpLocalReadRoots (per the SYNC
comment). The dir holds only session plan files; sibling ~/.qwen files
such as settings.json stay confirmation-gated, pinned by a new test.

Refs #6237

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 02:43:23 +00:00
Nothing Chan
a76a0feedb
fix(core): give plugins from the same repository distinct extension ids (#7676)
* fix(core): give plugins from the same repository distinct extension ids

getExtensionId() hashed only the normalized repo URL, so two plugins
installed from one marketplace repo (e.g. dotnet and dotnet-test from
github.com/dotnet/skills) collided on the same id and the second install
failed with 'Extension id belongs to "dotnet", not "dotnet-test"'. The
plugin name is now appended to the hash input when present.

Because the formula change would strand existing installs (the store
would mint a fresh default policy under the new id, resetting activation
state, and the orphaned old policy would trip the name-conflict guard on
update/uninstall), ExtensionStore.ensureInitialized now re-keys a stored
policy to a loaded identity's id when the id is unknown but the unique
name matches and no loaded extension still owns the old entry.

Fixes #7568

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

* test(core): pin case-insensitive matching in the store re-key migration

Seeds a policy under 'DotNet', re-keys it via an identity named 'dotnet',
and asserts the policy moves with its activation state and the stored
name normalizes — so a future simplification to case-sensitive matching
fails a test instead of silently orphaning differently-cased installs.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 02:42:36 +00:00
Nothing Chan
a3d78c9594
fix(core): tell the model when the user manually exits plan mode (#7682)
* fix(core): tell the model when the user manually exits plan mode

While plan mode is active the plan-mode system reminder is re-injected
on every model-bound turn, so after a manual exit (Shift+Tab,
/approval-mode, /plan, ACP mode switch) the reminder just silently
stops appearing — a non-signal models do not reliably notice. The
model's most recent context still says plan mode is active, so it keeps
calling exit_plan_mode and gets stuck (#7671, problem 1).

Config.setApprovalMode now queues a one-shot notice on every
PLAN -> non-PLAN transition except the approved exit_plan_mode flow
(which passes the new fromApprovedPlanExit option); re-entering plan
mode clears a stale notice. GeminiClient's system-reminder assembly
consumes the flag on the next turn and injects an explicit "the user
has manually switched out of plan mode" reminder naming the new mode.

Fixes the first half of #7671; the deny-message half is #7673.

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

* test(core): pin the leader-approval exit option and the exit-reminder text

Adds the two review-suggested pins: a successful leader-approved exit
asserts setApprovalMode receives { fromApprovedPlanExit: true } (only the
regular approval site was pinned), and getManualPlanExitSystemReminder
gets dedicated prompts tests covering the rendered mode name, the
exit_plan_mode prohibition, and the reminder envelope.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-25 02:42:04 +00:00
qwen-code-dev-bot
2cc7da4cef
fix(core): exit_plan_mode returns guidance error from execute() instead of permission deny (#7673)
* fix(core): exit_plan_mode returns guidance error from execute() (#7671)

When the model calls exit_plan_mode outside plan mode (e.g. after the
user manually switched modes via Shift+Tab), it previously got a
generic permission deny error that gave zero guidance.

Changed getDefaultPermission() to return 'allow' instead of 'deny'
when not in plan mode — this is a state issue, not a security issue,
so the permission layer should not block it. The execute() method now
checks approvalMode !== PLAN (with no approval snapshot) and returns
a context-aware errorResult telling the model what happened and what
to do instead.

This also fixes the YOLO mode issue where a permission deny error
was semantically wrong.

* fix(core): cover getConfirmationDetails() outside-Plan guidance path (#7671)

The guidance error for outside-Plan exit_plan_mode calls was only
reachable through execute(). A PM ask rule or a Plan-to-non-Plan
mode switch between permission evaluation and confirmation
construction routes through getConfirmationDetails() instead, which
still threw a generic error.

Extract outsidePlanGuidanceMessage() and reuse it in both
getConfirmationDetails() (throw) and execute() (errorResult) so
the model receives actionable guidance on every reachable path.

Add scheduler-level regression tests for both bypass paths and
update the design doc failure-behavior section.

* fix(core): use StructuredToolError and ToolErrorType for exit_plan_mode guidance (#7671)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-25 02:24:20 +00:00
ComplexSimply
451bcad991
fix(core): write a status sidecar so models stop misreading quiet background shells (#7669)
* fix(core): write a status sidecar so models stop misreading quiet background shells

A background shell whose child block-buffers stdout (typical for Python/ML
jobs without a TTY) keeps its output file at 0 bytes for its entire run.
The launch message only said "read the output file", so the model's sole
liveness heuristic became "empty file = dead process" — and it would
relaunch a still-running job, doubling CPU/GPU usage (#7626).

The registry now mirrors every entry into a machine-readable JSON sidecar
(`shell-<id>.status`, next to `.output`, same ReadFileTool auto-allow
rules): written on register and on every terminal transition, including
the `abortAll()` batch path so CLI exit settles sidecars too. Contents
are a pure projection of the entry (status, pid, command, cwd, ISO
timestamps, exitCode/error), written via temp-file + rename so readers
never see a torn document; write failures are logged and swallowed,
mirroring the output-stream error handling.

Written at the registry rather than in the tool because both launch sites
(executeBackground and the promoted-foreground path) and all four settle
entries converge there, and the single statusChange callback slot is
already taken by the UI. Both launch messages now name the status file
and teach the correct heuristic: do NOT infer liveness from the output
file; block-buffering keeps it empty while the process is alive (use
`python -u` / `stdbuf -oL` for live output).

Covered by registry unit tests (every transition, atomicity, failure
swallowing), launch-message assertions for both paths, and a real-spawn
integration suite replicating the reported scenario: quiet child →
sidecar `running` with pid while the output file is empty → `completed`
on exit, plus `failed` and `cancelled` variants.

Fixes #7626

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

* refactor(core): share the status-file guidance between both launch paths

Review on #7669 caught the two copies already drifting: the promote path
omitted the actionable unbuffering hint (`python -u` / `stdbuf -oL`) that
executeBackground includes. Extract the guidance into one helper used by
both paths so they cannot drift again, and pin the promote path's hint
with test assertions.

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

* refactor(core): use atomicWriteFileSync for status sidecar writes

Replaces the hand-rolled tmp+rename with the shared atomicWriteFileSync
utility, gaining EPERM/EACCES rename retry with exponential backoff,
random temp-file suffixes, and orphan cleanup on failure.

* Update packages/core/src/tools/shell.ts

Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>

* fix(core): skip fsync and restrict permissions on status sidecar (#7669)

* fix(core): force 0o600 and noFollow on status sidecar writes (#7669)

---------

Co-authored-by: ComplexSimply <rudy.arrowsong@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-25 01:20:19 +00:00
qqqys
541c80d678
feat(serve): expose workspace Channel management API (#7637)
* feat(serve): expose workspace Channel management API

* fix(cli): add workspace boundary guards to stop and remove channel methods (#7637)

* fix(cli): defer channel management runtime loading

* test(serve): cover channel management capability

* fix(serve): scope channel ownership to workspace and address review feedback (#7637)

* fix(serve): scope assertOwnedRuntime to workspace and add no-store to mutation routes (#7637)

* fix(serve): serve channel-types without resolving management service (#7637)

* fix(serve): add validateClient to GET /channels and expand invalid-client test coverage (#7637)

* fix(serve): prevent double 400 response when both name and body validation fail (#7637)

* test(serve): assert workspace reload is blocked

* test(serve): assert workspace reload is blocked (#7637)

* test(serve): expand route test coverage for review suggestions (#7637)

* refactor(cli): compose workspaceCommittedNames from workerFor to remove duplicated predicate (#7637)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-25 01:09:28 +00:00
Shaojin Wen
effb59ff73
fix(acp): sweep review worktree leases at the end of each prompt turn (#7694)
The ACP prompt path never entered promptIdContext — only the TUI
(useGeminiStream) and headless (nonInteractiveCli) entry points do — so
shell subprocesses in daemon sessions saw an empty QWEN_CODE_PROMPT_ID
and `qwen review fetch-pr` silently skipped recording its worktree
lease. A cancelled or errored /review in a Web Shell session therefore
left .qwen/tmp/review-pr-<n> and the qwen-review/pr-<n> branch behind
until the next review of the same PR happened to clean them up.

- Bind promptIdContext in #executePromptInner (enterWith, mirroring the
  sessionIdContext wrapper in #executePrompt) so lease creation works
  and shell subprocesses can identify the prompt that spawned them.
- Sweep the prompt's leases in the turn-wide finally, unconditionally
  like the headless path: the ACP loop runs whole turns, so unlike the
  TUI's per-continuation submitQuery this can never fire mid-review.
  No-op when the review's own cleanup step already released the lease.
2026-07-25 00:58:42 +00:00
Shaojin Wen
1f9318f974
feat(triage): stop in-agent CI polling, finalize evidence and approval after CI completes (#7693)
* feat(triage): stop in-agent CI polling, finalize evidence and approval after CI completes

The triage agent's Stage 2b polled pending checks for up to 10 minutes, but
this repo's unit suite runs ~30 minutes, so the poll always burned its full
budget, gave up with 'CI still running', and Stage 3 could then approve before
the suite finished (observed on a PR approved 12 minutes before its Test job
completed).

Split the wait out of the agent entirely:

- pr-workflow.md Stage 2b now forbids polling: fetch check-runs once, report
  pending checks honestly, and wrap the CI table in qwen-triage-ci region
  markers keyed to the reviewed SHA.
- Stage 3 defers a clean-verdict approval when checks are still pending: the
  comment carries an approve-on-green marker instead of an immediate APPROVE.
- New qwen-triage-finalize.yml fires on workflow_run completion of 'Qwen Code
  CI' / 'E2E Tests' and, with plain bash over the API (no model, no checkout),
  rewrites the marked table region with the settled results and posts the
  commit-pinned approval only when every check landed green — failing closed
  on red checks, a moved head, or a closed/draft PR, and flipping the triage
  status comment to say which way it resolved.

Markers are honored only in comments authored by the bot identity itself, and
check names (attacker-influenced on fork PRs) go through the same HTML-escape
chain the skill mandates for file paths.

Stage comments now land ~10 minutes sooner and the approval, when deferred,
lands at CI completion with full evidence instead of before it.

* fix(triage): address finalize review — broken red gate, table truncation, dead trigger

Review findings on the finalize workflow, all reproduced before fixing:

- Blocker 1: the RED jq used the array-first membership form, where | rebinds
  . and .conclusion indexes an array — jq exits 5 every run, RED comes back
  empty, [ "" -gt 0 ] errors, and control falls through to the approve path:
  a red CI auto-approved. The gate now binds the conclusion before the
  membership test (IN(...)), and the counters are numeric-validated so any
  future jq failure reads as 'cannot attest', never 'approve'.
- Blocker 2: the table rendered raw check-runs — on a real PR (96 runs, 35
  names, 68 skipped) alphabetical sort + head -60 truncated away every actual
  test job. table_rows now dedups per name (latest run), drops skipped rows,
  and sorts running/non-green first so the cap can only cut green rows.
  Replayed against the same PR: 96 rows -> 16, unit suite present.
- The approval gate now reads workflow runs filtered to event=pull_request
  (deduped per workflow) instead of head-SHA check-runs, which also carry
  long-running bot orchestration jobs that would wedge PENDING above zero at
  the exact moment the last CI workflow fires — silently dropping the
  deferred approval forever. The skill's Stage 3 PENDING count matches.
- E2E Tests had no pull_request trigger (dead entry); the workflows list is
  now exactly the six pull_request-triggered workflows, so the last finisher
  always re-fires the job.
- Head/state re-check moved before the red/deferred verdicts so a
  cancel-in-progress firing on a stale SHA cannot stamp a red status over
  the new head's comment; the still-deferred branch now updates the status
  comment instead of staying invisible.
- replace_region fails closed when the end-marker text only precedes the
  begin marker (awk END guard) — previously that shape truncated the comment
  body, eating the signature and reviewed-commit footer.
- Region content is deterministic (no run URL) so the no-op cmp works;
  empty run list or unavailable gate skips approval; comment wording fixed
  (workflow_run jobs are attributed to the default branch, so the self-check
  exclusion is belt-and-braces, not load-bearing).

Tests now execute the decision logic, not just grep for it: gate_counts and
table_rows run against fixtures covering every conclusion class, non-PR
events, re-run dedup, skipped filtering, ordering, and both marker-order
failure shapes. 30/30 passing.

* fix(triage): keep a stale finalize firing from clobbering the newer review's status comment

The status comment is deliberately not SHA-scoped (the triage workflow
creates it unscoped; scoping only the finalize side would orphan the
pairing), so a finalize firing for an old SHA that loses the race against a
newer head's green approval would overwrite the  status with a stale
warning. Guard the stale path: when the current head already carries bot
sha= markers (a re-review owns the status comment), stay silent; when the
head moved with no re-review yet — triage does not auto-rerun on
synchronize — the stale note is accurate and still posts. Closed/draft PRs
now just log instead of flipping the status.

* fix(triage): close the guardrail bypass and align the finalize table with the gate

Second review round, all four findings reproduced or confirmed before fixing:

- The approve-on-green marker was emitted in Step 1 while the fork-refactor
  GUARD only ran in Step 2 — a marker that slipped out on a fork refactor
  would have been honored by the finalize job on green CI, bypassing the
  guardrail entirely. GUARD now computes in Step 1 and gates the marker's
  emission, and the finalize job re-asserts it structurally from the PR
  state it already fetched (null head.repo = deleted fork = blocked), with
  a 'guarded' status message instead of an approval.
- table_rows now restricts check-runs to the suites of the same deduped
  event=pull_request workflow runs the gate trusts. Without it, 5 of 8
  rendered rows on this PR's own head were bot plumbing presented as CI
  evidence; with it, 115 raw check-runs reduce to exactly the 3 CI rows.
- A firing that saw PENDING>0 after the approval landed flipped the status
  comment back to 'deferred' with nothing to ever right it; the
  already-approved branch now repairs the status.
- Zero surviving table rows (failed runs fetch, missing suite ids) skips
  the region rewrite instead of blanking the agent's table, and
  replace_region refuses an empty region file (an unchecked getline would
  have deleted the region and its markers unrecoverably).

Nits: the house github.repository guard on the job, the table header
matches the skill template, and the run-URL stays out of the region so the
no-op cmp keeps working.
2026-07-25 00:58:16 +00:00
Shaojin Wen
3493dab8a0
test(web-shell): capture the git-mode new-branch sub-state in the visuals suite (#7672)
* test(web-shell): capture the git-mode new-branch sub-state in the visuals suite

The `git mode selector` visuals scenario captured the composer chip and the
opened three-mode popover, but stopped there: selecting an option used to
dismiss the popover ~100ms later (the click bubbled through the React tree out
of the portaled content to the composer surface's onClick → core.focus() →
Radix focus-outside close), so the branch-name sub-state couldn't be shot
stably. #7668 fixed that dismissal, so it now can.

Extend the scenario to click "New branch", fill a valid branch name, and
capture the revealed input (validated) with its Create-branch affordance, in
both themes. All six git-mode captures are byte-stable across runs (0% pixel
diff). Match the option by role — its label is split across a name and a
description span, so getByText('New branch') is ambiguous (also fixed in #7668).

Beyond covering a state the preview never showed, this doubles as a visual
regression guard for #7668: if the popover ever dismisses on option-click again,
the input goes missing and the assertion fails here, not only in the screenshot.

* test(web-shell): strengthen git-mode branch assertion and trim comment (#7672)

* test(web-shell): harden git-mode branch capture into a real #7668 guard

Scope the New branch option to the popover locator and settle past the ~100ms dismissal window before re-asserting the popover and input stay visible, so a regression of #7668 hard-fails here instead of only producing a wrong (visually reviewed) screenshot. Mirrors the proven guard in web-shell.git-mode.spec.ts.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-25 00:57:56 +00:00
qqqys
1183a4c821
feat(core): add Goal v3 runtime orchestration (#7664)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(core): add Goal v3 runtime orchestration

* fix(core): remove unused goal runtime paths
2026-07-24 19:46:38 +00:00
易良
65b4a5a383
fix(ci): update qwen in the runner's active npm prefix (#7689)
* fix(ci): update qwen in runner npm prefix

* test(ci): cover writable runner prefix install
2026-07-24 19:11:41 +00:00
jinye
055523afab
feat(core): Align GenAI content telemetry fields (#7667)
* feat(core): align GenAI content telemetry fields

Capture provider-final GenAI messages and tool payloads with the shared ARMS/OpenTelemetry field contract, and retire equivalent private content aliases.

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

* fix(telemetry): address content observability review

Keep ACP tool telemetry best-effort, improve diagnostics and sensitive-off coverage, and document finish-reason and tool-description behavior.

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

* docs(telemetry): clarify optional tool parameters

Document that invalid optional parameter schemas are omitted while required tool identities remain ordered and complete.

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

* docs(core): clarify telemetry compatibility behavior

Document the changed semantics of deprecated helpers and the reason the fallback context shadows key operations.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-24 18:42:48 +00:00
Shaojin Wen
2da42099d4
fix(ci): don't fail triage cleanup when there is nothing to clean (#7688)
The 'Clean stale agent state' step strips non-allowlisted keys from the
persistent workspace's local git config through a
`git config --list | grep -ivE <allowlist> | while ...` pipeline. When
the config holds only allowlisted keys — the steady state on a reused
runner this step already sanitized, since actions/checkout's post step
removes its auth extraheader at job end — grep matches nothing and
exits 1. Under the default `bash -e` shell combined with the script's
`set -o pipefail`, that kills the step exactly when there is nothing
to clean, before any output, and every downstream triage step is
skipped (seen on run 30095456731, runner ecs-qwen-runner-sg-4).

Guard the grep with `|| true` so an empty match feeds an empty loop
instead of failing the job. Sanitization behavior is unchanged:
non-allowlisted keys (core.pager, include.path, `!`-aliases) are
still stripped.

The workflow test harness missed this because its allowlist test
re-assembles the pipeline without the step's shell flags and always
plants non-allowlisted keys first. Add a steady-state regression test
that runs the step's actual script under `bash -e` against a config
holding only allowlisted keys, plus a negative control that strips the
guard and demands the step die — red on the old workflow, green now.

Co-authored-by: verify <verify@local>
2026-07-24 17:44:25 +00:00
qqqys
b1ce0c2087
refactor(autofix): extract review verification runner (#7644)
* refactor(autofix): extract review verification runner

* test(ci): follow extracted autofix verifier

* docs(autofix): document the review verification runner env contract (#7644)
2026-07-24 16:46:24 +00:00
ComplexSimply
2051a41739
fix(cli): measure insight days and hours in local time everywhere (#7670)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (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
The /insight pipeline measured "a day" inconsistently: heatmap keys were
UTC (toISOString), the active-hours histogram bucketed by local hour in
the same loop, the ASCII renderer looked keys up by local date, and the
web renderer mixed a local grid cursor with UTC lookup keys. For non-UTC
users the ASCII heatmap lit the wrong cell, the web heatmap hung values
under weekday labels off by one day, and a UTC-string parse followed by
local normalization shifted days for negative offsets.

Per the maintainer decision on #6835, local time is now the single
convention, defined in one module — insight/dates.ts (dayKey /
parseDayKey / todayKey / hourOfDay) — that every producer and consumer
goes through: DataProcessor aggregation, streak calculation, the ASCII
renderer, the report filename, and the web renderer (via a same-shaped
twin, since web-templates cannot import from the cli package). If the
convention is ever revisited, that one file changes. parseDayKey builds
local midnight directly, fixing the negative-offset day shift; day diffs
round rather than floor so DST-shortened days still count as one day.

calculateStreaks also gets the semantics its empty guard block was
reaching for: currentStreak now means "the streak ending today or
yesterday, else 0" — previously a user inactive for months still showed
their last streak as current. Existing tests asserting the trailing-
streak behavior are updated, with new relative-date cases pinning
today/yesterday/two-days-ago boundaries.

No stored data migrates: heatmap, streaks, and active-hours are
re-aggregated from raw timezone-aware ChatRecord timestamps on every
run. The /stats services already key by local date, so this aligns the
repo on one basis.

Fixes #6835

Co-authored-by: ComplexSimply <rudy.arrowsong@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 15:41:13 +00:00
qwen-code-ci-bot
9e7acec863
chore(release): v0.21.0 (#7675)
* chore(release): v0.21.0

* docs(changelog): sync for v0.21.0

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-07-24 13:47:34 +00:00
Shaojin Wen
1eb1523ad4
docs(triage): scale PR verification to the change, add real-run depth (#7648)
* docs(triage): scale PR verification to the change, add real-run depth

/triage already drives a real tmux before/after (installed vs dev build).
But where it matters most that floor is not enough: a UI change's
highlight/caret is invisible in capture-pane text, build/test numbers from
a shared or symlinked tree can be environmental TS errors not the PR's,
and a perf claim went unmeasured.

Strengthen Stage 2b to scale the evidence to the change, pointing at
harnesses the repo already ships: the terminal-capture skill (node-pty ->
xterm.js -> pixel-accurate PNG) for UI/interaction changes with a named
on-screen oracle; a clean build for the numbers cited as evidence (a
symlinked node_modules surfaces spurious cross-package TS errors);
instrumenting the real built code for a measured before/after on perf
changes; and separating merge-blockers from standing follow-ups with a
reproducible methodology note. Docs/refactor PRs stay N/A.

* docs(triage): align Mandatory and BEFORE POSTING checks with N/A escape hatch (#7648)

* docs(triage): add N/A escape hatch to the tmux-output mandate (#7648)

* docs(triage): restate CI boundary and fork-sandbox ref in scale-evidence block (#7648)

* docs(triage): align tmux summary table and SKILL.md with conditional mandate (#7648)

* docs(triage): extend BEFORE POSTING gate to check evidence depth per PR type (#7648)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code <qwen-code@users.noreply.github.com>
2026-07-24 13:18:19 +00:00
Shaojin Wen
cc742ca31e
fix(web-shell): keep git mode popover open when picking branch/worktree (#7668)
Clicking the "New branch" or "Worktree" option dismissed the popover
instead of revealing the branch-name input / confirm button. The option
click bubbles as a React synthetic event through the portal up to the
composer surface onClick, which calls core.focus() and moves focus
outside the popover — tripping the Radix focus-outside dismissal (the
popover only guarded the pointer path via onInteractOutside).

Stop click propagation on the popover content, matching the composer
ToolbarPopover pattern in ChatEditor. Also fix the e2e selectors
(getByText('New branch') matched both the name and description spans)
and add a delayed still-open assertion so the flash-then-dismiss
regression cannot false-pass.
2026-07-24 12:36:34 +00:00
Shaojin Wen
1130865949
fix(web-shell): show full session names on hover (#7662)
* fix(web-shell): show full session names on hover

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

* test(web-shell): cover archived session tooltip attribute (#7662)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-24 12:10:58 +00:00
ChiGao
b30a4c27d9
fix(cli): align all TUI icon columns to a uniform 2-col width (#7633)
* fix(cli): align all TUI icon columns to a uniform 2-col width

Remove the extra left padding (paddingX=1) from ToolMessage and
CompactToolGroupDisplay that caused tool status icons (✓, ✗) to be
indented relative to the assistant message prefix (◆).

Reduce STATUS_INDICATOR_WIDTH from 3 to 2 so the tool status indicator
column matches the prefix width used by conversation and status
messages. Add flexShrink={0} to prefix boxes that were missing it.

Unify CompressionMessage, SummaryMessage, and MemorySavedMessage to
use the same fixed-width prefix box pattern (width=2, flexShrink=0)
instead of ad-hoc marginRight or minWidth approaches.

Restructure tool_use_summary in HistoryItemDisplay to use the
standard prefix box pattern instead of inline paddingLeft.

This ensures all icons (◆, ✓, ✗, ●, △, etc.) start at the same
column and all text content begins at a uniform offset, regardless
of whether the icon glyph is 1 or 2 terminal columns wide.

* fix(cli): address review feedback on icon alignment PR

- Remove dead COMPACT_GROUP_HORIZONTAL_PADDING constant (now 0)
  and its no-op subtraction from the height estimation formula.
- Reduce TMUX_SPINNER_FRAMES from 3-char to 2-char equivalents
  ('. ' / '..') so they fit the narrowed 2-col indicator box
  without overflowing in tmux sessions.

* fix(cli): force narrow presentation for ambiguous-width TUI icons

Append VS15 (U+FE0E, Variation Selector 15) to all East-Asian-Width
'Ambiguous' icon glyphs (◆ ● △ ○ ◎ ※ ∴ ∵ ★ ◉) via a central ICON
constant map in constants.ts. VS15 forces the terminal to render these
glyphs in their narrow (1-column) text presentation, matching
string-width's default ambiguousIsNarrow=true calculation.

Without this, CJK terminals render ambiguous glyphs as 2 columns while
Ink's layout engine allocates 1, causing a 1-column visual drift that
breaks icon/text alignment in the conversation view.

25 files updated to use ICON.XXX constants instead of raw string
literals for ambiguous icons.

* fix(cli): update test assertions for VS15 icon changes

- Fix 17 inline test assertions across 7 test files to match
  VS15-appended icon output
- Fix MarkdownDisplay.tsx: use stringWidth(prefix) instead of
  prefix.length to correctly measure display width of icons
  that include VS15 (a zero-width code unit that .length counts
  but stringWidth ignores)

* fix(cli): update CronPill/SettingsDialog for VS15 icon changes

- Fix CronPill test assertion for ◎ with VS15
- Update SettingsDialog to use ICON.CIRCLE_FILLED instead of raw ●
- Fix 10 SettingsDialog test assertions for ● with VS15
- Update SettingsDialog snapshots

* fix(cli): address maintainer review feedback on PR #7633

- Fix ToolGroupMessage width regression: split innerWidth so
  ToolMessage gets full contentWidth (no longer needs -2 since
  paddingX was removed) while ToolConfirmationMessage keeps -2
  (it still has its own padding={1})
- Fix stale '3-char' comment in GeminiRespondingSpinner → '2-char'
- Add comment explaining minWidth vs width asymmetry in
  ToolStatusIndicator (minWidth allows growth for tmux spinner
  frames and test mocks)

* fix(cli): add VS15 to ◐ (U+25D0) ambiguous icon in todo components

◐ (CIRCLE WITH LEFT HALF BLACK) is East-Asian-Width Ambiguous.
Added ICON.CIRCLE_LEFT_HALF constant and updated StickyTodoList
and TodoDisplay to use it.

* fix(cli): strengthen test assertion and clarify VS15 comment

- HistoryItemDisplay test: restore spatial assertion to
  toContain('◆\uFE0E Hello') instead of split checks
- constants.ts: clarify that VS15 is zero-width in string-width
  but forces narrow terminal presentation

* fix(cli): update StickyTodoList test assertion for ◐ VS15

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
2026-07-24 11:48:55 +00:00
Shaojin Wen
f8014652a5
test(triage): regression-guard the triage workflow, and make the git cleanup an allowlist (#7660)
Guards the security-critical invariants of qwen-triage.yml that broke silently
once already — the `settings_json:` input name was wrong, so the action dropped
it and the review agent ran with the full default toolset and no deny list.
A new `node:test` suite (wired into the shared HELPER_TESTS list both CI paths
run) asserts: the `settings:` input name (never `settings_json:`), the
tools.core registration whitelist and the deny list, the fork-PR runner routing
invariants, and the git exec-vector cleanup.

It also flips that cleanup from a best-effort denylist — which kept missing new
families (pager, filter.*, includeIf subsections, url.*, credential…) — to a
keep-known-safe allowlist: unset every local config key that isn't plumbing
actions/checkout needs (repo format, remote, branch, fetch/gc/pack/index,
safe.directory, extensions, submodule url/active/branch — not submodule.*.update,
which can be `!cmd`). This closes the whole exec-vector class, including knobs
not yet enumerated. The harness runs the workflow's actual allowlist pattern
against a scratch repo to prove it unsets every exec family and preserves the
checkout plumbing.

Co-authored-by: verify <verify@local>
2026-07-24 11:02:19 +00:00
qqqys
4c2b48115b
feat(core): add bounded Goal evidence verification (#7639) 2026-07-24 09:54:24 +00:00
Shaojin Wen
d550aeabee
fix(triage): actually restrict the CI review agent's tools (#7647)
The `settings_json:` input on the Qwen Code action does not exist — the action
reads `settings:`. The block was silently dropped ("Unexpected input(s)
'settings_json'" in the run logs), so the triage agent ran with the full
default toolset and no restrictions in a job that carries a write PAT.

- Rename to `settings:` and express it in the current schema (tools.core +
  permissions.deny); verified to load through the real settings pipeline
  (11 tools registered, 106 deny rules active).
- Deny interpreters, build tools, shells, network binaries, path execution, and
  the git/gh write subcommands that execute configured commands or materialize
  PR code. This is defense-in-depth, NOT a boundary: under the action's --yolo a
  command denylist cannot be one (`$(...)` is not reliably deny-matchable, and
  exec can hide in other commands) — see the in-file note. The real controls are
  the skill's no-PR-code rule, ephemeral runners for fork PRs, and the git
  exec-vector cleanup added here. Residual token-exfil-under-injection risk is
  documented, with token isolation flagged as the structural follow-up.
- Route fork PRs (and comment/dispatch reruns, which may target fork PRs) to
  ephemeral hosted runners so a steered agent cannot persist on the shared ECS
  pool. Issue triage and same-repo PRs keep the ECS pool.
- Wipe stale git hooks/config/aliases each run before checkout.

Depends on the skill change that switches test evidence to the CI API and the
CHANGELOG fetch to `gh api`; merge that first so the npm/curl denials are
harmless.

Co-authored-by: verify <verify@local>
2026-07-24 09:29:52 +00:00
qqqys
a0b9512003
feat(core): add configurable image generation models (#7607)
* feat(core): add configurable image generation models

* fix(core): keep undici out of ACP bundle

* fix(core): address review feedback for image generation (#7607)

- Use loadUndici() instead of direct import('undici') in downloadPng
  to handle esbuild CJS bundling where named exports are unavailable
- Check response.ok before parsing JSON body so non-JSON error pages
  (e.g. 502 HTML) produce structured HTTP status errors
- Use matched.baseUrl instead of matched.registryBaseUrl in the image
  model handler for consistency with the vision model handler
- Add zh-CN and zh-TW translations for ImageGen tool display name,
  model command description with --image, and all new image model
  UI strings (fixes i18n test failures)
- Add tests: redirect-following path, max redirect limit, non-JSON
  error body, permission-disabled registration, imageOnly vision guard

* fix(cli): add missing English i18n keys for image model feature (#7607)

* fix(core): prevent signed-URL leak via error cause chain in image-gen (#7607)

* fix(core): add web-shell image_gen display name and fix safe-mode re-read (#7607)

---------

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 Autofix <qwen-code-autofix@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-24 09:03:18 +00:00
qqqys
4c3bf1c13a
feat(channels): run loops in daemon workers (#7641)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(channels): run loops in daemon workers

* fix(channels): preserve loop workspace rejection
2026-07-24 08:14:57 +00:00
Shaojin Wen
2a97c55ac8
feat(review): follow the session output language for runtime task names (#7653)
/review's user-facing runtime — the Agent tool description each subagent
runs under, the progress narration between steps, the Step 6 terminal
report — was English regardless of the session's Output Language, while
the posted artifacts already follow the PR's language. Split the two
registers: everything that lands on the PR keeps matching the PR
(the prDescriptionHasHan machinery is unchanged), and everything the
local user watches live now follows the output language preference,
falling back to the user's input language under auto.

The description parameter is display-only — the delivery and coverage
checks compare recorded prompts and role keys, never descriptions — so
the CLI-built blocks still travel verbatim, and the Verdict: line and
the final 'Review complete:' line stay untranslated. The roster and
reverse-audit round preambles restate the rule at the point of action,
where an orchestrator actually constructs the Agent calls.

Co-authored-by: verify <verify@local>
2026-07-24 07:48:50 +00:00
Shaojin Wen
713a083aea
fix(triage): make unattended PR review static — read CI via API, never run PR code (#7646)
The triage skill instructed the review agent to run the PR's tests (npm/tmux)
in the CI worktree. That executes untrusted PR code in a job whose environment
carries a write PAT, and the "tests pass" evidence it produced came from
self-run commands rather than the PR's own CI.

Scope the behavior by trigger:

- Unattended CI runs (GITHUB_EVENT_NAME set): never build or run PR-derived
  code. Stage 2 test evidence comes from the PR's own CI checks via the API
  (check-runs + the failing job's log excerpt). Real-scenario TUI coverage is
  left to the isolated `@qwen-code /tmux` job.
- Local invocation only (no GITHUB_EVENT_NAME): drive the app in tmux as before.

Also: never present the author's self-reported results under a testing heading —
attribute them as a claim if referenced at all; and fetch the CHANGELOG via
`gh api` instead of `curl`.

The companion workflow change that enforces these restrictions with tool/permission
settings should merge after this, so its npm/curl denials never surprise the agent.

Co-authored-by: verify <verify@local>
2026-07-24 07:38:39 +00:00
jinye
39702ad2f8
fix(core): Preserve usage after empty OpenAI stream frames (#7650)
* fix(core): preserve usage after empty stream frames

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

* codex: address PR review feedback (#7650)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-24 07:09:01 +00:00
Shaojin Wen
66da87d7ad
ci(triage): surface live progress via an early status comment (#7654)
`@qwen-code /triage` runs the agent as one long workflow step, so the PR
thread stays silent until the first stage comment lands — the maintainer
can't tell it started or how far along it is. The agent's output already
streams live to the Actions log; the run link was only surfaced at the end.

Post a `stage=status` comment up front carrying that live run link, and
finalize the same comment (by marker, so a re-run reuses one comment) to a
terminal state at the end. Covers manual, auto (pull_request_target), and
dispatch triage. Best-effort — a failed status post never fails triage.

Co-authored-by: wenshao <wenshao@example.com>
2026-07-24 06:27:53 +00:00
dreamWB
5561ba1e92
fix(web-shell): honor locked workspace session actions (#7629)
* fix(web-shell): honor locked workspace session actions

* fix(web-shell): preserve scoped session actions

* test(web-shell): strengthen untrusted action coverage
2026-07-24 06:14:45 +00:00
顾盼
7ede46cff2
feat(cli): reference prior sessions via @ and add completion tabs (#7302)
* docs: design spec for @ session reference + tabbed completion UI

* docs: implementation plan for @ session reference + tabbed completion UI

* feat(cli): add @session: mention ref parser

* feat(core): add SessionReferenceService for slimmed session injection

* feat(cli): inject slimmed prior-session context on @session: mention

* feat(cli): surface prior sessions as @ completion suggestions

* feat(cli): tabbed category layout for @ completion dropdown

* feat(cli): ←/→ tab switching for @ completion categories

* docs: mark @ session reference design as implemented

* fix(cli): preserve assistant text on tool-call turns, keep newest turn under budget, guard stale session results

* docs(core): clarify SessionReferenceService slimming rules

* perf(cli): remove @ completion input latency from session listing

Dispatch file/MCP suggestions immediately and append prior-session
suggestions in a second render once the disk listing resolves, instead
of blocking the first render on session I/O. Cache the per-cwd session
listing for a short TTL so rapid keystrokes don't re-walk the chats dir.

* Revert "perf(cli): remove @ completion input latency from session listing"

This reverts commit 08bbbc21386ac0cd6db7a6f480f0fd59f2e6b8d4.

* fix(cli): harden session ref resolution and i18n tab labels (#7302)

* fix(cli): guard session ref I/O errors to never abort the turn (#7302)

* fix(cli): strip session: prefix in completion filter and align tab guard (#7302)

* fix(cli): address review feedback on session refs and tab tests (#7302)

* fix(cli): use SESSION_MENTION_PREFIX constant in completion filter (#7302)

* fix(cli): address review feedback on session refs and tab tests (#7302)

* fix(cli): address review feedback on session refs and tab tests (#7302)

- Constrain ctrl/command on completion tab-switch bindings so
  ctrl+left/right (word-jump) is not consumed for tab switching
- Derive a friendly title from the first user message in
  SessionReferenceService.resolve() when no explicit title is given,
  so UUID-based refs show meaningful context headers
- Fix dead conditional in plan doc code snippet

* fix(cli): surface real session lookup errors and test left tab switch (#7302)

* fix(cli): reset suggestion indices when active category tab disappears (#7302)

* fix(cli): address review feedback on session refs docs and tests (#7302)

* fix(cli): address review feedback on session refs and completion ordering (#7302)

* docs(cli): clarify title matching semantics in session reference design (#7302)

* test(cli): assert error card in ambiguous session title test (#7302)

* fix(core,cli): address review feedback on session refs (#7302)

- Replace O(N²) budget trimming with single-pass backward accumulation
  (maintainer-measured 6.8s → ~80ms for 8k-record sessions)
- Include header/marker overhead in approxTokens
- Prefer custom_title system record in deriveTitle over first user message
- Move session-ref detection before MCP resource ref to prevent collision
  with an MCP server literally named "session"
- Handle bare @session (no colon) as empty filter in completion
- Clamp activeSuggestionIndex when suggestion list shrinks
- Strengthen not-found test assertion to check ToolCallStatus.Error
- Fix unreachable mock state in InputPrompt tab-guard test
- Add tests: custom_title derivation, overhead in approxTokens,
  bare session filter, index clamping, activeCategory reset

* fix(core,cli): address review feedback on session refs (#7302)

- Fix approxTokens to exclude omission marker cost when not truncated
- Clamp visibleStartIndex when suggestion list shrinks to prevent empty dropdown
- Document merge order invariant for session suggestions in @ completion

* fix(core,cli): address review feedback on session refs (#7302)

* fix(core): address review feedback on session refs (#7302)

* fix(core,cli): address review feedback on session refs (#7302)

* fix(core): address review feedback on session refs (#7302)

* fix(cli): address review feedback on session refs (#7302)

* fix(core,cli): address review feedback on session refs (#7302)

- Wrap MCP category label in t() for i18n consistency
- Fix comment to match > 2 tab guard semantics
- Add test for Ctrl+arrow with exactly 2 categories (guard boundary)
- Fix budget loop to not reserve marker tokens when session fits

---------

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>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-24 06:11:48 +00:00