Commit graph

8858 commits

Author SHA1 Message Date
qqqys
22bb5e8b9f
feat(core): require an explicit user opt-in before the model launches a workflow (#9806)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* feat(core): require an explicit user opt-in before the model launches a workflow

The Workflow tool description carried judgement heuristics for orchestrating
well, but no rule about when not to orchestrate at all. Read on its own the
prose is encouragement, and a run that can dispatch up to the per-run agent
cap is a large spend to enter on inference rather than on a request.

Prepend a gate above the existing guidance: do not call the tool unless the
user asked for multi-agent orchestration, with the five forms that count as
asking under this project's own triggers -- the `workflow` keyword, the
user's own words, a skill or slash command, a named saved workflow reached
through `workflow('<name>')` or `scriptPath`, and a resume. Upstream's
`ultracode` marker is deliberately not among them: it does not exist here,
and naming it would enumerate a trigger no user can pull.

The fallback path is the load-bearing half. Without an offer-and-ask route
the model reads a refusal rule as "refuse", and a user who would have said
yes never gets asked, so the text ends by telling it to say what a workflow
would fan out over and let the user decide.

The agent cap is interpolated from `DEFAULT_MAX_AGENTS_PER_RUN` rather than
pasted, matching the rest of the description.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en

* chore: keep agent-core.ts as it is on main

The merge commit's pre-commit hook ran prettier over every file the merge
staged, not just the ones this branch changes, and reformatted a type union
in agent-core.ts. main's copy does not satisfy the repo's pinned prettier
3.6.1, so the hook produced a real diff in a file this PR has no business
touching. Restore main's bytes to keep the PR scoped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en

* test(core): pin workflow opt-in contract

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
2026-08-24 17:39:04 +00:00
qqqys
bf123a375a
fix(goal): converge the three continuation prompts on one guarded contract (#9834)
* refactor(goal): render Goal continuation prompts from one core renderer

The prompt sent when `runtime.finishTurn` schedules another Goal turn was
assembled independently in three hosts: the TUI's inline array in
`useGeminiStream`, and a `buildGoalContinuationParts` in each of the ACP
session and the non-interactive CLI. Three copies of the same four shared
lines have already drifted -- the TUI carries the anti-spoofing guard lines
but no objective, while ACP and non-interactive carry the runtime
continuation context but no guard lines.

Upcoming work adds further variants (an "objective was edited" announcement
and a budget wind-down prompt). With the text living in three places, every
new variant means three edits, which is precisely how the current drift was
produced. This moves assembly into `packages/core/src/goals/goal-continuation-prompt.ts`,
where a variant is a case in one function and the shared prefix exists once.
The two `buildGoalContinuationParts` helpers keep their names and signatures
and simply delegate.

This is a pure refactor: no prompt text changes. Each host still emits a
byte-identical string to the one it emitted before. The existing drift is
preserved deliberately and is left for a separate, behavior-changing
follow-up. The new unit test pins the complete rendered string for both
variants with and without verifier feedback, so any future edit to a line
surfaces as a test diff; the existing host tests pass unmodified.

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

* fix(goal): tighten continuation renderer contract

* test(goal): cover verifier feedback hosts

* refactor(goal): hoist Goal continuation parts builder into core (#9581)

* fix(goal): converge the three continuation prompts on one guarded contract

Every automatic Goal turn now renders the same prompt in every host: the
runtime-supplied goalId, revision and objective as an escaped JSON data
block, framed as untrusted task data, under both anti-spoofing guard
lines, followed by a line stating the block supersedes any earlier
objective in the conversation.

Before this change the drift ran the wrong way. ACP and non-interactive
interpolated the raw objective into a synthetic user-role turn carrying
neither guard line; the TUI carried both guard lines but dropped the
objective, so the host that guarded most gave up information and the two
that guarded least were the exposed ones. None of the three escaped the
objective, so objective text shaped like a tag could break out of the
surrounding prompt.

The prompt input collapses to a single flat shape, so the variant
discriminant and its unreachable-default arm are gone. `<`, `>` and `&`
are escaped inside the serialized JSON so an objective cannot close the
data block or open one of its own.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-24 17:38:29 +00:00
qqqys
ac109c9dd3
fix(core): relax uniqueItems in function schemas (#9869)
* fix(core): relax uniqueItems in function schemas

* fix(core): preserve schema map keys when relaxing tools

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
2026-08-24 17:15:28 +00:00
Shaojin Wen
02f5214939
test(cli): skip unreadable-ledger run-ledger test under root (#9913)
* test(cli): skip unreadable-ledger run-ledger test under root

The test simulates an unreadable ledger with chmod 000, but root
bypasses permission bits, so the read succeeds and the append goes
through, failing the test on every root run. Skip it there since the
property is untestable without DAC enforcement.

Fixes #9909

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

* test(cli): extend unreadable-ledger run-ledger skip to win32

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-24 16:44:27 +00:00
jinye
1fffa5108d
fix(acp-bridge): Disable permission timeout by default (#9933)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
npm cache producer / Save npm cache (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* fix(acp-bridge): disable permission timeout by default

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

* chore: regenerate settings schema

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

* docs(acp-bridge): fix stale timeout comment

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-24 16:08:44 +00:00
qwen-code-dev-bot
65c2bb01c0
fix(ci): narrow serve-ab's self-hosted wipe to the A/B checkout dirs (#9228)
* fix(ci): narrow serve-ab's self-hosted wipe to the A/B checkout dirs

'Wipe stale workspace before checkout' deleted the whole shared workspace
including the root .git, forcing the next job on that runner (e.g. a
fetch-depth: 0 review job) to re-download the full ~900 MB of history from
github.com. On the ECS pool's slow link that stalls checkouts for 20+
minutes and the fetches drop mid-pack often enough to read as hung runners
(2026-08-15: 20 orphaned tmp_pack files, ~6 GB, across 10 runners; one
checkout re-downloaded 890 MB in 19m45s).

serve-ab only builds inside its own head/ and base/ checkouts and never
reads the workspace root, so removing just those two dirs keeps the
anti-bleed guarantee without destroying the shared object store. The
ci-runner-routing pin now asserts the narrow scope and fails on a
whole-workspace wipe regression.

* test(ci): derive serve-ab wipe pin from the checkout paths (#9228)

Address review suggestions: the wipe targets are now derived from the
actions/checkout steps, and the wipe must be exactly one executed
(non-comment) rm line covering exactly those paths. Renamed checkout
paths, appended whole-workspace wipes, and commented-out or echo'd rms
now all fail the suite, while the reverted find-form still does.

* test(ci): pin the serve-ab wipe's full executed script and step order (#9228)

* fix(ci): match serve-ab's wipe step name and pin to its narrowed scope (#9228)

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

* fix(ci): exclude the shared root .git from serve-ab's self-hosted wipe (#9228)

Replace the head/base-only rm with the exclusion-based find suggested by the R5-3 review: everything is wiped except the shared root .git, which closes the recurring materializer-coverage class (R4-4/R5-1/R5-2/R5-3) at the layer that owns it — anything landed outside .git is removed by the next run's wipe, no matter how it was materialized. This removes a strict subset of what main's whole-workspace wipe removes, so it cannot regress base behavior, while keeping the .git whose destruction forced ~900 MB re-fetches on the ECS pool's slow link. The pin now also locks the execution chain (shell wrapper, no continue-on-error, no BASH_ENV at step/job/workflow level) per R5-4, and the obsolete clone-coverage scan is deleted.

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

* fix(ci): keep only a real .git and scrub its exec state in serve-ab wipe (#9228)

* fix(ci): follow a symlinked workspace root in the serve-ab wipe (#9228)

* fix(ci): defang the worktreeConfig bypass and anchor the serve-ab wipe scrub (#9228)

The kept-.git wipe tail had two measured holes: extensions.worktreeConfig
activates .git/config.worktree, a second local file that git config --local
neither lists nor unsets, so a planted core.hooksPath survived the allowlist
sweep; and after the heal unlinks a symlinked workspace root the step's CWD
still is the link's target, so the CWD-discovered scrub wrote outside the
workspace. Add qwen-triage's hardened defang pair (delete config.worktree,
unset the extension) and anchor every git call to $WS/.git. Both findings
reproduced locally before the fix; both suites pin the new lines and are
mutation-tested.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-24 13:27:38 +00:00
tlysanhuo
f7b936630f
fix(core): reject malformed Anthropic tool arguments (#9013)
* fix(core): reject malformed Anthropic tool arguments

Validate streamed tool input as protocol JSON and release tool calls atomically only after a trustworthy message stop. This prevents truncated arguments or open parallel tool blocks from leaking executable calls into retry consumers.

* fix(core): preserve max-token recovery for tool truncation

* fix(core): preserve interrupted Anthropic tool calls

* fix(core): recover empty max-token responses

* refactor(core): share stream retry predicates

* Merge main into tlysanhuo/fix-anthropic-tool-json

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
2026-08-24 13:05:31 +00:00
Harjoth Khara
95bdd46241
fix(config): accept output.format "stream-json" in the settings schema (#8966)
* fix(config): accept output.format "stream-json" in the settings schema

The runtime already reads and honors output.format: "stream-json" from
settings.json (normalizeOutputFormat -> OutputFormat.STREAM_JSON), and it
is a documented --output-format choice, but the settings schema listed
only text and json. The VS Code companion applies that schema to every
.qwen/settings.json, so it flagged a valid, working config as invalid.

Add stream-json to the source schema and regenerate the shipped
settings.schema.json. Same schema/runtime drift class as #8752.

Closes #8965

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

* fix(config): bind output.format schema values to OutputFormat and document stream-json

Apply the review's non-blocking suggestions:

- Schema options now use the OutputFormat enum constants the runtime's
  normalizeOutputFormat accepts, so the settings schema cannot silently
  drift from core.
- The full enum is pinned in the test (toEqual, sibling-test pattern)
  instead of a toContain probe.
- The format description — schema, regenerated VS Code schema, and the
  settings reference table — now notes that stream-json makes runs
  started with a prompt non-interactive (headless), and the docs table
  lists stream-json as a possible value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01US2APQw84vvQZZ4pZaKtzn

* test(cli): add OutputFormat to core mock factories that reach settingsSchema

settingsSchema.ts now reads OutputFormat at module load, so the two test
files that mock @qwen-code/qwen-code-core with a hand-built factory and
transitively import it need the enum in the mock, matching how they
already mock ApprovalMode.

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

* fix(config): derive the output.format test pin from the enum and document the argv-only gates

Address the round-2 review:

- The test pins the schema options against Object.values(OutputFormat),
  so a format added in core fails the test until the schema and the
  regenerated JSON follow; the schema comment now states exactly that
  instead of overpromising drift protection from the binding alone.
- The description, regenerated schema, and docs table note that flags
  validated at argv parse time (--include-partial-messages,
  --input-format stream-json) still require the explicit
  --output-format stream-json flag, since those yargs checks run before
  settings are loaded.

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

* test(cli): cover settings-driven stream-json output and name the flag in the docs note

Address the round-3 suggestions: a config test now exercises
output.format stream-json arriving from settings through loadCliConfig,
and the docs table names the --output-format stream-json flag the
argv-time checks require, matching the schema description.

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

* test(cli): pin argv-over-settings output format precedence with differing values

The existing precedence test used the same value on both sides, so an
inverted merge passed the suite. The new case sets settings stream-json
against argv text and asserts text wins.

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

* docs(test): match the enum-pin comment to the order-sensitive assertion

Apply the maintainer review nits: the comment now says array-derived,
order included, which is what toEqual checks, and the precedence test
drops a comment that restated its name.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-24 12:45:51 +00:00
Yu Zhang
2dbe806204
docs(sdk): fix query timeout example signature (#9867)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 12:40:05 +00:00
Yu Zhang
c216584e01
docs(core): fix goal judge timeout unit (#9861)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
2026-08-24 12:39:52 +00:00
Yu Zhang
703bb7a5cf
docs(core): fix read file paging default comment (#9863)
Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 12:39:50 +00:00
qqqys
772670ddb5
feat(skills): add find-simplifications sweep skill (#9384)
* feat(skills): add find-simplifications sweep skill

Adds a repo-scoped skill for a periodic, evidence-first sweep of the
codebase for surface that no longer has a consumer: dead files and
components, orphaned locale keys, exports nothing calls, scaffolding
whose feature left.

It fills a gap between two things that already exist. The bundled
/simplify is anchored on a diff, so it cannot see surface that
accumulated across releases; /repo-hygiene targets defects and
explicitly bans "cleaner / more modern / more consistent" edits. Neither
covers code that is correct but that nothing needs.

The design is deliberately conservative. A run's deliverable is a
comment on a tracking issue, not a pull request, and a PR follows only
for a candidate a maintainer has said yes to, one candidate at a time.
Everything reachable from the core package's exports map, every settings
key, and every protocol shape is report-only, because no grep inside
this repository can prove those have no consumer.

Three documents: the charter, boundaries and recurring-run design in
SKILL.md; the survey phase, taxonomy, proof protocol and worked examples
in references/survey.md; the landing checklist, verification table and
CI blind spots in references/land.md.

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

* fix(skills): correct find-simplifications review findings (#9384)

* fix(skills): correct find-simplifications review findings, round 4 (#9384)

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

* fix(skills): correct find-simplifications review findings, round 5 (#9384)

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

* fix(skills): close the two out-of-repo reachability holes in the proof protocol

R6-1: the Landable catch-all row decided landability by in-repo import
reachability alone, so surfaces whose consumers live outside the repo passed
all eight gates with a complete-looking evidence package. Two measured
entrances: `packages/zed-extension` is a store-shipped `extension.toml` plus
LICENSE/SVG/README with no in-repo importer, and `.github/` holds 52 workflow
files with 0 in-repo `uses: ./.github/workflows/…` references — a live one such
as `docs-page-action.yml` cleared every gate. Both are now excluded from the
catch-all row and carry their own Report-only rows, alongside
`packages/vscode-ide-companion` and `packages/chrome-extension`, which sit in
the same family and survived only through incidental grep hits.

R6-2: proof step 3 — the protocol's only published-surface gate — enumerated
core/audio-capture/channels only, stopping two packages short of the Territory
table's own "out-of-repo consumers" row. It now covers `packages/sdk-*` and
`packages/acp-bridge` (`release-sdk.yml:297` npm-publishes sdk-typescript with
`--access public`; `release-sdk-python.yml:346` ships to PyPI;
`release-sdk-java.yml:206` deploys to Maven), and names the Territory table as
the authoritative list so the two cannot drift apart again.

Every citation re-measured on this branch: zed-extension holds exactly four
files, `.github/workflows` holds 52 files with 0 self-referencing `uses:`, and
all three release workflows publish at the cited lines.

* docs(skills): carve out the three out-of-repo-consumer surfaces, and fetch before landing

R7-1: the Landable catch-all row decided landability by in-repo import
reachability alone, so three surfaces whose consumers live outside the repo
fell into it with no Report-only row of their own:

- `packages/webui` — npm-published under its own name (`publishConfig.access:
  public`, no `private` flag), so registry consumers are real;
- `packages/core/vendor/**` — listed in `packages/core/package.json` `files`
  and copied into the CLI bundle by `scripts/copy_bundle_assets.js:284-304`.
  Its only runtime consumer, `getBuiltinRipgrep()`
  (`packages/core/src/utils/ripgrepUtils.ts:164-175`), assembles the path from
  segments, so a grep for the literal path returns zero hits and the consumer
  proof passes vacuously — while the `rg` binaries have zero import edges by
  definition, making them prime slice-3 whole-file-orphan candidates;
- `packages/web-shell` — copied into the bundle
  (`copy_bundle_assets.js:389-397`) and served to browsers by `qwen serve`;
  `scripts/prepare-package.js` gates the release on its dist.

Add a Report-only row for each, name them in the catch-all row's exclusion
list so the two cannot disagree, and add the `@qwen-code/qwen-code` tarball
itself to the rationale paragraph's published-surface enumeration.

R7-2: the land phase never fetched before cutting the branch, so §1's
re-verification could re-certify the survey's own base. `git checkout -b`
succeeds against a stale cached ref exactly as `git worktree add` does — the
failure mode `references/survey.md` § 0 already documents and guards with
`git fetch origin || exit 1`, which §1's "steps 2 through 5" cross-reference
excludes. Since assent routinely arrives days or weeks after the survey, that
drift is the design norm: a consumer that landed on main in the interim stays
invisible, every step re-passes, and the squash-merge breaks it.

Require the fetch in the branch precondition, and say in §1 that the branch
base is current `origin/main` only because of it.

Docs-only change (3 markdown files, no code); mutation verification does not
apply. Every factual claim added was re-verified against the tree: `npm`
manifests for the publish flags, `copy_bundle_assets.js` for the bundle
copies, `ripgrepUtils.ts` for the segment-assembled path. `prettier --check`
clean (the Territory table re-padded on write).

* fix(skills): correct find-simplifications review findings, round 9 (#9384)

* fix(skills): correct find-simplifications review findings, rounds 10-11 (#9384)

* fix(skills): correct find-simplifications review findings, round 12 (#9384)

* fix(skills): correct find-simplifications review findings, round 13 (#9384)

* fix(skills): correct find-simplifications review findings, round 14 (#9384)

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

* fix(skills): correct find-simplifications review findings, round 15 (#9384)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.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>
2026-08-24 12:37:05 +00:00
qqqys
24db7f6ef2
feat(review): say when the approach, not the patch, is the open question (#9340)
* feat(review): say when the approach, not the patch, is the open question

Every finding /review emits is anchored to a `file:line` in the current diff.
That is what a finding is — and it means a review can report where an approach
leaks, but never that a different approach would retire all of the leaks at
once.

Measured: one change to `extractAndStripMeta` took three attempts across two
PRs. #9097 (3 rounds, 18 findings) added a timeout to the vm call; #9136 (6
rounds, 56 findings) moved the walk inside the vm and ended up spawning a child
process per call, growing 228 -> 920 source diff lines. #9325 landed it in one
commit by not evaluating the literal at all. All 74 findings were individually
correct, and every one of them went away with the mechanism.

The signal was already there and filed as the wrong kind of thing: `did not
converge within the reverse-audit round cap` appeared four times across the two
PRs, as a coverage gap — "we did not finish looking" — rather than as a
conclusion about the change. Nothing was responsible for reading it as "stop
patching".

Add one advisory paragraph, and one clause on the terminal verdict line, when a
non-Approve round is past the round threshold AND its source diff has grown at
least 3x since the review first measured it. This round's round-cap stop rides
along as corroborating text when present; it is never a trigger on its own.

It is deliberately not a finding. Findings are what the autofix loop consumes,
and that loop patching each finding in turn is the pattern being interrupted —
a finding here would be fixed rather than read. It addresses the human deciding
what happens next, so it is a body paragraph and a verdict-line clause, it adds
no cap, and it never moves the event.

The baseline is a baseline, not the previous round's size: 228 -> 920 across six
rounds is ~1.3x per round, which no per-round delta would notice, but 4.0x
cumulatively. `Ledger.src0` records the first measurement and is carried forward
unchanged, so a diff that later shrinks cannot rewrite its own baseline. It is
the one marker field that survives truncation — the ruling that withholds an
anchor from a partial finding list does not extend to a measurement of the diff.

Known limits, documented rather than papered over: it cannot see across pull
requests, so the three-attempt shape that motivated it would have fired only on
a second forgeable persisted counter; and it is retroactively blank, staying
silent until a PR has posted two rounds after this ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(review): suppress approach signal for downgraded approvals

* fix(review): measure approach growth over full diff

* fix(review): validate approach signal evidence

* fix(review): pin approach-signal boundaries and validator coverage

Round-5 review findings: boundary tests for the round threshold,
growth factor, and source-diff floor; the round-cap corroborating
clause and its zh rendering; src0 survival through the pr-context
persist seam and the incremental marker carry-forward; artifact
validator refusal/absence tests for approachSignal; design doc
firing list names the pre-cap verdict.

* fix(review): clamp the approach signal's round at the ledger cap (R9-1)

The signal computed its displayed round with an unclamped `prevRound + 1`
while the ledger marker stamp and the deferred-suggestions clause both
clamp with `Math.min(prevRound + 1, LEDGER_MAX_ROUND)`. `parseLedger`
accepts `round == LEDGER_MAX_ROUND`, so a side file at the cap is
representable and carries forward: one composed body announced
"⚠️ Round 10001" beside a marker stamping `"round":10000`, and the
terminal verdict line printed 10001 too — the doc comment in this same
diff claims all three consumers cannot disagree "at the cap included".

The new test pins the cap for the third consumer, mirroring the existing
deferred-clause cap test; mutation-verified that reverting the clamp
turns it red with `round: 10001`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-24 12:27:26 +00:00
易良
d128998779
fix(core): accept quiet post-tool-result completions after retry exhaustion (#9196)
* fix(core): accept quiet post-tool-result completions after retry exhaustion (#9026)

Models that legitimately end a turn silently after a tool result (valid finish reason, no visible content) trip the NO_TOOL_RESULT_PROGRESS guard; the #7039 retry budget gives them four identical retries and then the error aborts the whole run — the dominant failure mode across ~390 headless rollouts on GPT/Gemini-family models (291/96 hits vs 0 on Qwen). Keep the retry-first behavior and, once the budget is exhausted, accept the quiet completion instead of re-throwing: the MAX_TOKENS variant stays fatal, a placeholder keeps user/model alternation well-formed when the accepted turn produced nothing, and the JSONL record carries the same text as history. Explicit per-attempt one-shot flag; continuation streams share the same acceptance through the shared retry wrapper.

* fix(core): scope quiet completion retry flag per attempt

* test(core): pin quiet completion retry edges

* fix(core): close round-1 findings on quiet-completion acceptance (#9026)

- consume the one-shot acceptance where the retry outcome is rescheduled,
  not where the attempt starts: re-arm the flag in the rate-limit /
  transport-replay / transport-continuation / reactive-compression
  continue branches whenever either invalid-stream budget is exhausted,
  and arm on any budget-exhausting invalid-stream type (the accept gate
  independently requires a tool-result continuation with no visible
  progress and a non-MAX_TOKENS/SAFETY/RECITATION finish)
- keep SAFETY/RECITATION-blocked quiet turns fatal: content-filtered
  continuations (OpenAI content_filter / Anthropic mapped to SAFETY)
  must not end silently with no user-visible signal
- record accepted quiet turns from one source: hoist acceptedTurnParts
  and build the JSONL record from the same parts history keeps, so an
  accepted turn whose only parts are non-text (inlineData/fileData) no
  longer desyncs transcript from history on --resume
- fatal-MAX_TOKENS test: attach the rejection assertion before advancing
  fake timers so the file's test command no longer exits 1 on an
  unhandled rejection
- regression tests: armed attempt surviving a transport replay, a 429,
  and a mixed final error type; inlineData-only accepted turn recording;
  SAFETY quiet turns staying fatal

* fix(core): close round-3 findings on quiet-completion acceptance (#9026)

R1-1: the invalid-stream retry branch now rearms the one-shot quiet
acceptance like the other four rescheduling paths, so an armed attempt
that fails with PROTOCOL_TAG_LEAK no longer strands the acceptance.
Arming is keyed to the transient bucket only: quiet completions surface
as a transient-type error, so a tag-leak-only budget exhaustion can no
longer arm prematurely and bypass the #7039 retry-first invariant.

R2: the accept gate keeps the whole content-filter finish-reason family
fatal (SAFETY/RECITATION/BLOCKLIST/PROHIBITED_CONTENT/SPII and the
IMAGE_* variants), mirroring mapGeminiFinishReasonToOpenAI's
content_filter grouping; the constants move into genai-compat's
FinishReason alongside MAX_TOKENS.

Also drops the dead contentText placeholder write (acceptedTurnParts is
the single source for the JSONL record and history push), parameterizes
expectStreamExhaustion so the fatal tests reuse it (clearing the
vitest/valid-expect lint failures), hoists chatWithRecorder to the
shared scope, pins RECITATION via it.each, and adds regression tests
for the tag-leak rearm, tag-leak-only no-arm, and the armed-continuation
rearm site.

* fix(core): keep IMAGE_OTHER fatal in the quiet-completion gate (#9026)

mapGeminiFinishReasonToOpenAI also groups IMAGE_OTHER into
content_filter; include it in CONTENT_FILTER_FINISH_REASONS so a
native-route IMAGE_OTHER block cannot be silently accepted either.

* fix(core): map Anthropic refusal stop_reason into the content-filter family (#9026)

R6-1: mapAnthropicFinishReasonToGemini had no entry for Anthropic's
refusal stop_reason, so it fell through to FINISH_REASON_UNSPECIFIED —
defined (passes the NO_FINISH_REASON throw), not MAX_TOKENS, and not in
CONTENT_FILTER_FINISH_REASONS. Once the retry budget was spent, the
armed attempt therefore accepted a provider refusal as a quiet
"(empty content)" completion, masking the safety decision the gate was
written to keep fatal. Map refusal to SAFETY alongside content_filter
so the existing content-filter family keeps it fatal.

Regression tests: converter refusal-to-SAFETY mapping (mutation-
verified) and a geminiChat armed-attempt test asserting
NO_TOOL_RESULT_PROGRESS exhaustion with no placeholder turn recorded.

* fix(core): fail closed on unknown quiet finish reasons

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-08-24 11:50:25 +00:00
Shaojin Wen
d1cfd87683
feat(review): promote language-pitfall and wrapper/proxy checks out of Agent 1a (#9805)
* feat(review): promote language-pitfall and wrapper/proxy checks out of Agent 1a (#9788)

Split the two checks folded into Agent 1a's line-by-line brief into dedicated
Step 3A roles at high effort: Agent 1d (language-pitfall scan, always) and
Agent 1e (wrapper/proxy routing, rostered when the plan's wrapperSignal is
true — a capture-time vocabulary heuristic that fails safe: only an explicit
false keeps it out, so version-skewed plans still owe the check). The roster,
check-coverage and agent-prompt all read the gate from the plan, so a run that
skips either agent is named. Briefs, SKILL.md, and the user-facing code-review
doc updated; 1a keeps its walk minus the two clauses.

* fix(review): address round-1 feedback on the 1d/1e split (#9805)

* fix(review): address round-2 feedback on the 1d/1e split (#9805)

* fix(review): address round-3 feedback on the 1d/1e split (#9805)

* fix(review): address round-4 feedback on the 1d/1e split (#9805)

* fix(review): address round-5 feedback on the 1d/1e split (#9805)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-24 11:15:58 +00:00
顾盼
37cedea5b2
feat(computer-use): replace built-in tools with bundled skill (#9856) 2026-08-24 11:05:23 +00:00
易良
3892ca32ca
fix(core): map OpenAI-compatible finish_reason case-insensitively (#9884)
OpenAI-compatible gateways that front Gemini backends can return the
Gemini-native finish_reason spelling in uppercase (`STOP`, `MAX_TOKENS`).
The previous case-sensitive lookup mapped these to
FINISH_REASON_UNSPECIFIED, silently disabling MAX_TOKENS truncation
recovery.

Fold the reason to lowercase before lookup and add an explicit
`max_tokens` alias for the Gemini-native spelling.

Resolves #9882
2026-08-24 09:55:57 +00:00
Yu Zhang
6a21c436d6
test(core): align lazy generator vertex test (#9888)
Use an interface-supported method to exercise lazy Vertex initialization after the content generator interface was narrowed.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
2026-08-24 09:24:26 +00:00
jinye
b5aec6691e
fix(serve): Canonicalize Live task bridge session IDs (#9819)
* fix(serve): Canonicalize Live task bridge session IDs

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

* test(serve): Pin canonical Live owner lookup

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

* fix(cli): harden mixed-case live session routing

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-24 09:11:38 +00:00
Shaojin Wen
b4a54a9f05
fix(ci): move undeletable workspace residue aside instead of leaving it (#9868)
A leftover the pre-checkout sweep cannot delete does not just fail the job
that finds it — it poisons the checkout of every later job scheduled onto
that runner. Measured on run 32621267802: residue from a review probe
(`.qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/.qwen/tmp/review-pr-666`)
survived on one shared-pool member, and two unrelated PRs then died at
Checkout with the same EACCES on the same path. The sweep's own steps had
reported success: their ladder ends at a warning, so a workspace it cannot
repair is handed to actions/checkout unchanged.

The recovery the ladder was missing is a rename. Unlinking an entry needs
write permission on the directory holding it — exactly what foreign-owned
residue denies on a pool member without passwordless sudo — while renaming
needs it only on the two parents, and the workspace root is always the
runner's own. So a tree that defeats rm, chmod, and chown still moves out of
the way, and the checkout finds nothing to trip on. The destination sits
next to the workspace so the rename cannot cross a filesystem and degrade
into copy-then-unlink, and the warning names it, because the tree still
needs a human.

Reproduced in a Linux container with real foreign ownership (residue owned
by root, workspace by the runner user, sudo unavailable): before, the step
warns `leaked .qwen`, the tree stays, and a checkout-style wipe fails with
`Permission denied`; after, the tree is quarantined, the wipe succeeds, and
the warning points at where it went.
2026-08-24 08:58:16 +00:00
Dragon
43d46be912
refactor(core): shrink the content generator interface (#9676)
* refactor(core): shrink content generator interface

* refactor(core): remove orphaned request-tokenizer estimator cluster

Removing countTokens from both providers deleted the last production
consumers of RequestTokenEstimator. Delete the orphaned cluster:
requestTokenizer.ts (330), imageTokenizer.ts (534), types.ts (36), the
directory barrel (11), and both test files (608 lines). Also drop the
inert vi.mock of requestTokenizer.js left in client.test.ts and the
stale dimension-extractor cross-reference in review/lib/assets.ts.

textTokenizer.ts and supportedImageFormats.ts stay: converter.ts, pdf.ts,
and fileUtils.ts still consume them and the core barrel re-exports them.

* docs(design): sync lazy-google-genai-loading record with shrunk interface

countTokens and useSummarizedThinking no longer exist on ContentGenerator,
so the design record for the lazy-wrapper architecture must not keep
advertising them: list the three remaining shared async operations, drop
the useSummarizedThinking sentence and the summarized-thinking item from
the consumer audit and Verification section, and add a dated note
recording the interface shrink from PR #9676.

* ci: record cd-cua-driver.yml size growth in .size-baseline

Same latent main-side violation as fixed in #9682: #9587 grew the
workflow without a baseline update; record the new size as the check
message directs (precedent #9747).

* docs: finish scrubbing tokenizer references after estimator-cluster removal

Follow-up to 0ee17632c7/1871bb5b81 (review round 2):
- supportedImageFormats.ts header and getSupportedImageFormatsString doc
  no longer describe a tokenizer decode/metadata-extraction stage; the
  list is now documented as the vision-input acceptance list, with token
  accounting noted as the flat DEFAULT_IMAGE_TOKEN_ESTIMATE.
- web-shell-image-drag-and-drop.md's BMP rationale no longer claims
  ImageTokenizer parses BMP dimensions; dated sync note added stating
  BMP support rests on SUPPORTED_IMAGE_MIME_TYPES plus converter
  passthrough since PR #9676.

* docs: drop tokenizer from the BMP test-plan line

Follow-up to 18f08c0924: the test plan still required converter/tokenizer
focused tests for image paths; the image-tokenizer estimator cluster was
removed in PR #9676 (text tokenizer is unaffected and out of scope here).
2026-08-24 08:30:18 +00:00
Ryan Gabriel
6420d69d14
test(acp-cron): kill whole process tree on cleanup to stop ENOTEMPTY flakes (#9815)
* test(acp-cron): kill whole process tree on cleanup to stop ENOTEMPTY flakes

The recurring cron job under QWEN_CODE_TEST_CRON_FAST can drop a fresh
file into the fake QWEN_HOME while rmSync walks it, failing cleanup
with ENOTEMPTY. The direct agent.kill() only signals the CLI process;
grandchildren spawned by the CLI keep running and can still write.

Spawn the CLI in its own process group and signal the group during
cleanup (taskkill /T on Windows). Also widen the rmSync retry window.

Same flake tracked by #9766, #9643 and #9555.

* test(acp-cron): kill whole process tree on cleanup to stop ENOTEMPTY flakes

The recurring cron job under QWEN_CODE_TEST_CRON_FAST can drop a fresh
file into the fake QWEN_HOME while rmSync walks it, failing cleanup
with ENOTEMPTY. The direct agent.kill() only signals the CLI process;
grandchildren spawned by the CLI keep running and can still write.

Spawn the CLI in its own process group and signal the group during
cleanup (taskkill /T on Windows). Replace the widened rmSync built-in
retries with fresh-walk retry attempts.

Same flake tracked by #9766, #9643 and #9555.
2026-08-24 08:23:33 +00:00
易良
e0d933b23e
refactor(core): make derived Config ownership explicit (#8100)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
SDK Python / Classify PR (push) Has been cancelled
SDK Python / SDK Python (3.10) (push) Has been cancelled
SDK Python / SDK Python (3.11) (push) Has been cancelled
SDK Python / SDK Python (3.12) (push) Has been cancelled
* refactor(core): define derived config ownership

* docs(core): align derived config ownership scope
2026-08-24 07:48:49 +00:00
易良
27285a5243
refactor: centralize approval mode contracts (#9796)
* refactor: centralize approval mode contracts

* fix: align Python SDK import grouping

* test: restore approval mode exports in CLI mocks

* fix: close approval mode drift gaps

* test(cli): preserve core exports in serve mocks

* fix: close approval mode review gaps

* test(cli): complete permission request fixture

* test(sdk): match approval mode route

* test(approval): close review coverage gaps

* test(sdk): cover approval mode global scope path
2026-08-24 07:46:50 +00:00
易良
a60cbbc54a
refactor(core): make utils/ a leaf layer (#9778)
* refactor(core): make utils/ a leaf layer

Eliminate every runtime (value) upward import from
packages/core/src/utils production modules so utils/ can become a leaf
layer with no runtime dependency on the rest of core.

Two mechanisms, no behavior change:

- Relocate domain-coupled modules out of utils/ into their owning
  module (agents, config, core, memory, services, tools), and move
  generic constants/types that live elsewhere into utils/. All
  `git mv` moves keep history; every import that pointed at a moved
  file is rewritten.

- Extract the remaining value imports as small leaf modules inside
  utils/ (AuthType, isTool, ToolErrorType, DEFAULT_QWEN_MODEL) and
  re-export them from their original owners so cross-package consumers
  are unaffected. doesToolInvocationMatch moves into shell-utils, its
  only production consumer.

Only type-only imports now cross the utils/ boundary. The two deferred
inversions in debugLogger (Storage, getTraceContext) are stateful and
left for a follow-up.

* chore(core): enforce utils/ leaf layer with lint rule

Add architecture/no-core-utils-upward-import, which flags runtime
(value) imports that leave packages/core/src/utils. Type-only imports,
sibling utils imports, and external package specifiers stay allowed;
the two deferred debugLogger inversions (config/storage,
telemetry/trace-context) are carried on an explicit allowlist.

Enable the rule as an error on core sources and cover it with
Linter-based tests.

* fix(core): restore iconv-lite tree-shaking for sync-file-encoding

The utils leaf-layer refactor moved sync-file-encoding from utils/ to services/, but the esbuild tree-shake plugin still matched the old ./utils/ specifier, so its sideEffects:false marker no longer applied and the ACP startup closure regained a static iconv-lite import. Point the onResolve filter at the new ./services/ path.

* fix(ci): catch stale integration imports earlier

* fix(core): close utils boundary review gaps

* fix(core): close self-reference boundary gaps

* ci: re-trigger after self-hosted runner checkout EACCES
2026-08-24 07:43:01 +00:00
易良
5b3830b46c
feat(vscode-ide-companion): adopt WebShell transcript as the default timeline (#9719)
* feat(vscode-ide-companion): reuse WebShell transcript UI behind experimental flag

Bridge ACP session/update notifications into the shared SDK daemon transcript reducer and render the result with the WebShell transcript component, gated on qwen-code.experimental.webShellTranscript (default off).

The WebShell renderer and its heavy transitive dependencies (echarts, mermaid, shiki, codemirror, katex) are lazily loaded via esbuild code splitting, so the default configuration keeps the ~700KB webview bundle unchanged.

* fix(vscode-ide-companion): grant wasm-unsafe-eval for shiki WASM when WebShell transcript enabled

* feat(vscode-ide-companion): adopt WebShell transcript as default timeline

Drop the experimental flag and the legacy MessageList renderer. The companion timeline now always renders through the shared WebShell transcript component, fed by ACP session/update notifications via the SDK daemon transcript reducer (lazy loaded through esbuild code splitting).

The flag-gated wiring is removed: the qwen-code.experimental.webShellTranscript setting, the conditional CSP/body attribute in WebViewContent, and the legacy MessageList path in App.tsx (~850 lines). The webview CSP now grants wasm-unsafe-eval unconditionally for Shiki's Oniguruma WASM.

* fix(vscode-ide-companion): reset WebShell transcript state on session switch

The experimental useAcpTranscript hook only consumed transcriptUpdate
messages, so its reducer state survived session boundaries. When the
extension switched sessions it kept the webview mounted and replayed the
newly-selected session through ACP, causing the previous session's blocks
to merge with the new replay (e.g. user text "alpha" from session A leaked
into session B as "alphabeta").

Reset both the reducer state and the rendered blocks on the same
boundaries the legacy message flow uses: qwenSessionSwitched (sent before
the ACP replay of the selected session) and conversationCleared (new
session). Adds a regression test that replays two sessions with a switch
between them.

* fix(vscode-ide-companion): harden WebShell transcript session boundaries

- reset the transcript on `conversationLoaded` too, closing the same
  cross-session leak the previous commit fixed for `qwenSessionSwitched`
  and `conversationCleared` (agent reconnect posts only this boundary)
- track the active session id and drop late `transcriptUpdate` frames
  whose `sessionId` no longer matches, so a previous session's trailing
  frames cannot contaminate the next session's timeline
- seed the transcript from cached messages carried by
  `qwenSessionSwitched` so offline restores and load-failure fallbacks
  render their history instead of a blank timeline
- dispatch `assistant.done` on `streamEnd`/`sessionLoadComplete` so the
  final assistant/thought block of a turn (or history replay) does not
  stay `streaming: true` forever

* fix(vscode-ide-companion): adopt live ACP session id after load-failure fallback

* fix(vscode-ide-companion): echo user prompt into WebShell transcript

* fix(vscode-ide-companion): keep WebShell transcript expanded and clear of the composer

* fix(vscode-ide-companion): surface local error and interrupt notices in the transcript area

* fix(vscode-ide-companion): restore file-link opening from the WebShell transcript

* fix(vscode-ide-companion): restore contributed copy commands for the WebShell transcript

* fix(vscode-ide-companion): add localOnly marker to TextMessage state type

* fix(vscode-ide-companion): restore /insight progress card and report link in the transcript UI

* fix(vscode-ide-companion): finalize in-flight tools on timeout and pin session-switch seeding guard

Map streamEnd reasons timeout/session_expired onto the reducer's error reason so abandoned mid-tool turns no longer spin forever (ceuI). Add qwenSessionSwitched cases with no messages field and an empty cache array; the no-messages case fails when the seeding guard is forced true, pinning its false side (ceuN).

* fix(vscode-ide-companion): remove unreachable editMessage backend and dead submit options

The user-message edit/rewind UI was dropped in the WebShell-transcript migration, leaving editTargetTurnIndex/onSubmitted options in useMessageSubmit and the full editMessage/rewind flow in SessionMessageHandler unreachable. Remove the dead options, the editMessage dispatch case, the rewind/snapshot flow with its recovery branches, and their tests (R1-8 direction b).

* fix(vscode-ide-companion): drop write-only loadingMessage bookkeeping

The waiting-message renderer was removed with the WebShell transcript migration and the user prompt is echoed into the timeline at send time (bd09e19d86), so the loadingMessage string was write-only dead state. Keep the isWaitingForResponse flag (submit gating / cancel) and pin its API surface (R1-19 direction b).

* fix(vscode-ide-companion): align waiting-flag pin test with the argument-less setter

* fix(vscode-ide-companion): echo attached images into the transcript timeline

The prompt carries pasted/attached images as ACP resource_link blocks,
which the transcript reducer cannot render (no inline data), so user
images vanished from the timeline while the attach path stayed alive.
Read each saved prompt image back from disk and echo it alongside the
text echo as an inline user_message_chunk image part (the daemon-echo
content shape), which the shared reducer folds into the user block and
the WebShell renderer already displays. Unreadable images are skipped
without breaking the send.

* fix(vscode-ide-companion): track live VS Code theme for the transcript

webShellTheme was snapshotted once at mount via useMemo with an empty
dependency array, so switching the VS Code color theme left the
timeline on the stale theme (VS Code updates data-vscode-theme-kind on
<body> in place without reloading the webview). Hold the theme in state
and refresh it with a MutationObserver on the body theme attributes.

* fix(vscode-ide-companion): copy every transcript block kind and map ambiguous row keys

- Copy All Messages now includes tool, shell, user_shell, and status
  blocks via getBlockCopyText, matching the pre-PR copyAllMessages
  handler which included formatted tool calls (review 5001842059 S-1).
- findBlockByRowKey prefers an exact id match and otherwise the longest
  matching block id, so one block id that dash-prefixes a sibling (e.g.
  `a` vs `a-1`) can no longer capture the sibling's row key (S-4).

* fix(vscode-ide-companion): drop whitespace-only cached transcript rows

cachedMessageToNotification rejected empty strings but admitted
whitespace-only content, which the reducer turns into an empty block
when seeding history from cached rows. Reject content that trims to
nothing (review 5001842059 S-2).

* fix(vscode-ide-companion): ship missing third-party notices in NOTICES.txt

Extend generate-notices.js so the regenerated NOTICES.txt carries the
attribution texts it previously only pointed at or dropped:

- Append license files from a package's licenses/ directory (echarts'
  Apache LICENSE references licenses/LICENSE-d3 for its embedded
  d3-derived files; the BSD-3-Clause text is now shipped).
- Append a package's NOTICE file when present (Apache-2.0 §4(d)),
  covering echarts' Apache Software Foundation attribution.
- Accept string-form package.json repository values (full URLs and
  GitHub shorthand) instead of emitting "(No repository found)".
- Fall back to the standard MIT text (copyright holder from package.json
  metadata) for MIT-declared packages that ship no license file.

* fix(vscode-ide-companion): show a recoverable error state when the transcript chunk fails to load

* test(vscode-ide-companion): gate the transcript blocks wiring into the WebShell renderer

* test(vscode-ide-companion): gate the transcriptUpdate forwarding from agent to webview

* fix(vscode-ide-companion): correct canonical MIT disclaimer wording in notices fallback

* fix(vscode-ide-companion): surface locally generated notices and aborted sends in the transcript UI

* fix(vscode-ide-companion): correlate streamEnd with the active request in the transcript hook

* fix(vscode-ide-companion): pin transcript session guard at clear/load boundaries with the fresh session id

* chore(ci): refresh cua workflow size baseline

* fix(vscode-ide-companion): split file links on raw # before percent-decoding

normalizeExplicitFileLink decoded the whole value before splitting on #, so an encoded %23 in a filename was treated as a fragment delimiter and truncated the path. Split on the raw # first and decode the path and fragment parts separately; the file:// branch decodes only the path component. resolveFileLinkFromAnchor also no longer runs URL decoding/fragment logic over the anchor-text fallback: the text is a literal path, which keeps the /export 'export (#1).html' links (whose file: href the sanitizer strips) clickable.

* fix(vscode-ide-companion): stamp cached history rows as discrete transcript messages

Cached-history seeding emitted each row as a bare *_message_chunk with no promptId/sourceRecordIds/_meta, so the shared reducer merged runs of consecutive same-role cached rows (Tool Result / telemetry / Plan rows per turn) into one plain-concatenated block, and a dropped whitespace-only user row let different turns fuse. Stamp every synthesized cached row with the reducer's existing anti-merge marker (_meta.qwenDiscreteMessage) so offline restores render the same discrete blocks as live replays.

* fix(vscode-ide-companion): resolve tool-group copy rows and copy tool content parts

Copy Message silently failed on every tool row: web-shell keys tool_group rows as msg:tg-<block id>, which findBlockByRowKey never matched. Strip the tg- prefix before the existing exact/longest-prefix matching; merged groups share the first block's key, so a group row resolves to the group's first tool block (documented). The tool case of getBlockCopyText also serialized only title + details (the input summary), dropping the output text and diffs the timeline renders; walk block.content and append text parts and ---/+++ diff renderings like the pre-PR formatToolCallForCopy did, restoring Copy Message / Copy All parity for tool rows.

* fix(vscode-ide-companion): close remaining transcript regressions

* fix(vscode-ide-companion): hide internal image references
2026-08-24 07:41:51 +00:00
易良
9ef32c5f3c
fix(core): make team shutdown a leader-only tool (#9401)
* fix(core): make team shutdown a leader-only tool

send_message carried an optional single-value enum `type:
['shutdown_request']` described as "structured message type for control
flow". Models filled it while composing an ordinary report; the call was
then rejected leader-only and the report content was discarded, leaving
the teammate retrying a report the leader never received.

Split control from content at the tool boundary rather than validating
the field harder. `type` is removed from send_message entirely, and
shutdown becomes request_shutdown, which createToolRegistry skips for
subagent-context registries — so a teammate has no declaration for it and
cannot emit the call at all, instead of emitting one and being refused.

The mailbox wire format is unchanged: sendStructuredMessage still writes
`type: 'shutdown_request'` with `from: LEADER_NAME`. Only the tool
surface moved.

Fixes #9276

* fix(ui): cover request_shutdown in tool display-name drift guards

The new leader-only request_shutdown tool landed without entries in the
drift-guarded display surfaces, breaking CI:

- web-shell TOOL_DISPLAY_NAMES (toolFormatting.drift.test.ts) and the
  zh badge label in client/i18n.tsx (toolFormatting.test.ts parity)
- cli toolDisplayName locale entries enforced by i18n/index.test.ts and
  check-i18n key parity (en identity / zh / zh-TW)

* fix(core): close the leader-only hole the review found, and register the display name

Three criticals from review, all real.

The leader-only guarantee did not hold. "Enforced by absence" covers a
registry that was *built* with forSubAgent, but runSingleDispatch's workflow
fast path hands a subagent the parent leader's registry untouched — and that
one does contain request_shutdown. So a workflow subagent could request a
shutdown as the leader. The runtime guard now uses
isSubagentLikeExecutionContext rather than isTeammate, which covers every
subagent-like context instead of only a teammate identity.

RequestShutdown had no toolDisplayName locale entry, failing the CLI i18n
guard, and no entry in web-shell's manually synced TOOL_DISPLAY_NAMES, failing
that package's drift guard. Both added, matching the locales that carry
SendMessage.

And the suggestions: a config test asserting the tool is present in a leader
registry and absent from a forSubAgent one — the property everything rests on,
which nothing checked; the deliberate 'ask' permission default pinned; the
empty and whitespace recipient guard covered, since the schema admits both;
and the dead requestShutdown mock left in send-message.test.ts's helper type
removed.

Verified: 534 config tests, 30 across request-shutdown and send-message.

* fix(core): exclude request_shutdown from subagent and teammate tool sets

* fix(i18n): translate the RequestShutdown tool display name in Catalan

The entry was left as raw English while every sibling team tool is
translated.

---------

Co-authored-by: yiliang114 <jinjing.zzj@gmail.com>
2026-08-24 07:36:18 +00:00
jinye
b2d0687213
feat(serve): add --open-with-auth (#9738)
* docs(serve): propose ephemeral auth for --open

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

* docs(serve): address ephemeral auth review

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

* docs(serve): clarify asset pre-check boundary

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

* docs(serve): centralize token selection plan

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

* docs(serve): make ephemeral auth opt in

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

* docs(serve): align ineligible-browser handling with manual-URL fallback

Browser-launch eligibility is a heuristic with common false negatives,
so it is no longer a hard pre-listen gate: an ineligible environment
warns (naming the tripped signal), starts the daemon, and prints the
fragment-bearing manual URL, matching the launch-failure recovery.
Also pin the generation breadcrumb with planned test assertions.

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

* feat(serve): add opt-in ephemeral auth for --open

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

* feat(serve): replace ephemeral auth with --open-with-auth

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

* docs(serve): clarify temporary token storage

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

* docs(serve): clarify ephemeral token persistence

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

* codex: address PR review feedback (#9738)

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

* codex: address PR review feedback (#9738)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-24 07:33:05 +00:00
Harjoth Khara
dbf7382c8f
fix(memory): scan uncapped when selecting forget candidates (#9530)
* fix(memory): scan uncapped when selecting forget candidates

Recall moved to the uncapped scanner in #8716; forget did not. A document
ranked past the 200-document cap could be recalled and injected into the
prompt but never forgotten.

Forget now scans uncapped, so its candidate universe matches recall's. The
model-selection prompt renders every candidate, so it gets its own bound of
400: literal query matches first, then the most recently modified remainder.
The heuristic fallback keeps scanning the full uncapped list.

Indexer, status, and extraction stay capped on purpose, and the two design
docs that recorded forget as capped now say otherwise.

Refs: #9378

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

* fix(memory): give each scope its own share of the forget prompt

Review round 1. The 400-candidate bound ranked both scopes into one recency
budget, so a store whose project entries are all newer than its user entries
seated no user memory at all. The capped scanners this replaced ran per scope,
so each scope always had seats. That made an old user entry unselectable by the
model while recall could still inject it, which is the same asymmetry the PR
set out to close.

Each scope now keeps a 200-candidate quota and whatever a smaller scope leaves
is handed to the other. Within a scope, literal query matches rank first and
both groups are ordered newest first, so truncation is deterministic instead of
scan-order, and the bound logs when it drops candidates.

Also from review: the query normalisation and match predicate are now shared
with selectByHeuristic so the two cannot drift; the user scan gets the
best-effort guard recall.ts and extractionAgentPlanner.ts already carry; and
the docstring and design docs no longer claim an unconditional guarantee the
bound does not provide.

Three tests, each verified against the mutation it is meant to catch: global
ranking drops the user ids, an ascending sort drops the newest filler, and
handing the fallback the bounded list returns 400 of 450 matches.

Refs: #9378

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

* fix(memory): bound the unconfirmed forget path and drop the silent scan guard

Review round 2, all suggestions.

MemoryManager.forget passed limit: MAX_SAFE_INTEGER and deletes without
confirmation. With an uncapped scan and a heuristic fallback that substring
matches the whole store, a one-character query matched nearly every entry in
both scopes, where the capped scanners had held that same failure to one scan's
worth of candidates. The limit is now the prompt bound, restoring the old
ceiling.

Round 1 added a best-effort catch on the user scan. That was wrong on two
counts: scan.ts caps after reading and ordering the whole tree, so uncapping
adds no read exposure to justify it, and swallowing the failure made forget
report "no entries matched" for a scope it never read, then act on that answer
by deleting. Reverted, with a comment saying why forget differs from recall
here: a missed injection is recoverable, a missed deletion is not.

normalizeForgetQuery now delegates to normalizeSummary so query matching and
the post-selection re-match cannot drift apart, and one design-doc sentence no
longer implies only semantic matches fall off the bound.

Two tests, each verified against its mutation: the quota split is now exercised
with both scopes over quota, where dropping it to 150 seats 250 project entries
instead of 200; and the delete ceiling fails at 401 removals if the unbounded
limit comes back.

Refs: #9378

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

* fix(memory): split forget's deletion seats per scope, and decouple the ceiling

Review round 3.

The deletion ceiling added last round truncated the heuristic fallback in
candidate order, and listIndexedForgetCandidates pushes every user entry ahead
of every project entry. With 450 matching user entries and 50 matching project
ones and the side query down, forget deleted 400 user entries, zero project
ones, and reported success. That is the reachability asymmetry this PR exists
to remove, moved into the delete path. The per-scope allocation the model
prompt already used is now shared with the heuristic, so each scope keeps its
share of the limit and a smaller scope's unused seats go to the other.

The ceiling is also its own constant now rather than an alias of the prompt
bound. Resizing the model prompt is a cost decision and resizing this is a
blast-radius decision; sharing one constant let the first silently widen the
second.

Two tests, each checked against its mutation: the 450-user/50-project shape
returns zero project matches under a plain slice, and oldest-first ranking
inside a scope drops that scope's newest entry from the prompt.

Refs: #9378

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

* test(memory): pin the forget split at a small limit and the heuristic's own order

Cross-review found both new tests mutation-survivable. Every case used a
400 limit, so hard-coding a 200 per-scope quota instead of deriving it from
the budget still passed, and the recency case let the side query succeed, so
it pinned the model prompt's ranking rather than selectByHeuristic's own
comparator.

One case at limit 5 with the side query failing covers both: it asserts the
3/2 split, which only holds if the quota comes from the budget, and that each
scope contributes its newest entry, which fails if the comparator is reversed.
Both mutants verified failing.

Refs: #9378

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

* refactor(memory): share the forget recency comparator and log a bound deletion

Review round 4, both suggestions.

The mtime comparator was the last thing the model path and the heuristic path
each typed for themselves, after this branch had already hoisted the query
normaliser, the match predicate and the per-scope allocator so the two could
not drift. Each site has its own test, so a one-sided ordering change would
have updated its own test, passed CI, and left the sibling stale. Now one
definition.

The deletion cap also bound silently. The prompt bound warns when it truncates;
the path that actually deletes did not, so a forget that removed 400 of 500
matches reported success and left no record of why recall kept injecting the
rest. It now says so.

No test for the new warning: it is a debug log line, and asserting on it would
pin the wording rather than the behaviour.

Refs: #9378

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-24 07:31:01 +00:00
qqqys
c7c4dc80e0
feat(core): add the output-style layer to the system prompt (#9565)
* feat(core): add the output-style layer to the system prompt

Introduces output styles: a named prompt section that changes how the
agent reports its work, selected per session. This is the core half —
the registry, the four built-in styles, and the prompt wiring. Nothing
selects a style yet; the picker, the settings key, and the per-turn
reminder follow separately.

The style lands at the end of the stable `base` layer: after the mandates
it refines, and still ahead of every context/volatile layer, so the
prompt prefix stays cacheable for the whole session.

Two precedence rules, both following what the file already does for
QWEN_SYSTEM_IDENTITY_MD:

- A QWEN_SYSTEM_MD override wins. That file is a full, user-owned prompt;
  layering our section onto it would defeat the override.
- The QWEN_WRITE_SYSTEM_MD dump stays a pure base prompt. A style sits on
  top of a base, so baking it into the dump would apply it twice once
  that file is fed back through QWEN_SYSTEM_MD.

`keepCodingInstructions: false` lets a style replace the base outright,
for styles that are not about software engineering at all. All four
built-ins keep it true — Proactive in particular changes how much you
plan and ask, not what you are allowed to do, and says so in its prompt
so it does not read as a second permission knob next to ApprovalMode.

Subagents and arena runs deliberately do not inherit the main session's
style: it would multiply Explanatory's insight blocks across every child,
and skew an arena comparison that is supposed to isolate the model.

* fix(core): narrow keepCodingInstructions to the section it names

`keepCodingInstructions: false` replaced the whole base prompt, which
took the safety rules down with the workflow guidance: a non-coding
style lost `# Executing actions with care`, the mandates, the tool
guidance and the tone section along with the part it meant to drop.

It now omits exactly one section — the software-engineering workflow
guidance, split out as `getSoftwareEngineeringTasksSection()`. Every
other section stays under every style. A style adjusts how work is
reported; it never switches off the rules for taking risky actions.

Two smaller corrections in the same area:

The identity sentence now points at the style when one is active
("responding according to your Output Style below") instead of claiming
the agent specializes in software engineering under a style that says
otherwise. A `QWEN_SYSTEM_IDENTITY_MD` override is still inserted
verbatim — that wording is distributor-owned and not ours to rewrite.

The style section is headed `# Output Style: <name>` rather than
`# <name> Style Active`. The heading is the contract a custom style file
will rely on once user and project styles load: the file body becomes
the prompt verbatim and the heading is what names it.

The per-turn reminder is now every style's, not just the two that spell
one out. `turnReminder` overrides the generic wording rather than
deciding whether a reminder exists at all — an Explanatory session
drifts back to terse answers as readily as a Concise one does.
`getOutputStyleTurnReminder()` renders the line the injection site will
use.

Because the style now feeds the base prompt rather than only being
appended to it, the QWEN_WRITE_SYSTEM_MD dump builds its own unstyled
copy, preserving the invariant that the dump is a reusable base.

The prompts.test.ts snapshots are unchanged, which is the evidence that
a session with no style selected still gets a byte-identical prompt.

* test(core): pin the output-style layer where review round 1 found it unpinned

Two coverage gaps from the r1 review, both confirmed by mutation probes:

R1-1 (prompts.test.ts): the `QWEN_SYSTEM_IDENTITY_MD` override combined with
an active output style was pinned by nothing — the identity-override tests
pass no style and the `outputStyle parameter` tests never stub the override.
Skipping the style append under an override kept all 105 tests green while
silently dropping the user's style for every deployment shipping an identity
override. The new case asserts the override text wins verbatim, the styled
identity sentence is skipped, and `# Output Style: Concise` still lands.

R1-2 (contextCommand.test.ts): the `config.getOutputStyle()` forwarding into
`getCoreSystemPrompt` was never exercised with a style active — every config
mock returned `undefined`. Deleting the argument kept the suite at 16/16
while `/context` undercounted system-prompt tokens by the style section. The
new case bills a `Concise` style and asserts the estimate grows by that
section.

Mutation verification: skipping the style append under an identity override
fails only the new prompts case (1 failed | 105 passed); deleting the
`config.getOutputStyle()` argument fails only the new contextCommand case
(1 failed | 16 passed).

* fix(core): align output style prompt consumers

* test(core): pin Learning output style modes

* test(core): pin Learning style in ACP prompts

* fix(core): align output-style doc comment and Concise description

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-24 07:19:57 +00:00
Stellar鱼
f241c19ace
fix(core): support per-provider stream idle timeout (#9795) 2026-08-24 06:46:25 +00:00
dreamWB
717ad101e2
feat(web-shell): add async submit preparation (#9802)
* feat(web-shell): add async submit preparation

* fix(web-shell): keep failed-prompt retries out of the daemon retry path

The failed-prompt retry reused `retry: true` only to skip prepareSubmit,
but the flag is also forwarded to the daemon, whose retry branch skips
recording the user message — the retried turn ran and was answered, yet
its user prompt never reached the transcript. Skip preparation with an
internal skipPrepareSubmit flag instead; the Ctrl+Y turn-error retry
keeps `retry: true`, where the daemon semantics are correct. Also
classify the retry payload from the prepared prompt so a host rewrite
that changes slash-ness still arms the turn-error retry.

Adds focused coverage for the resolvePreparedSubmit fallbacks,
preparation-time staleness cancellation on both paths, prepareSubmit
rejection, and the queued empty-prepared guard.

* fix(web-shell): disarm stale retry state after slash-prepared submits

* test(web-shell): pin slash-prepared disarm clears with attachment and stash variants

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-Coder <qwen-coder@alibabacloud.com>
2026-08-24 06:13:51 +00:00
ShiZai
4f53c7d160
fix(core): route debug logs through sessionIdContext before global session (#9538)
* fix(core): route debug logs through sessionIdContext before global session

In daemon/ACP mode one process hosts many sessions. Config creation
overwrites the process-wide debug log session, so logs for session A
were written to session B's file after B's Config was created.

ACP sessions already wrap execution in sessionIdContext.run(sessionId).
Make debugLogger.getActiveSession() consult that context before falling
back to the global session, while keeping runWithDebugLogSession /
runWithoutDebugLogSession overrides first.

Fixes #9535.

* fix(core): bind sessionIdContext in ACP control dispatch and refresh latest alias

- Wrap AcpAgent.extMethod dispatch in sessionIdContext.run(sessionId, ...)
  when a sessionId parameter is present, so control-plane handlers like
  qwen/control/session/recap route debug logs to the targeted session.
- Make debugLogger.getActiveSession() consult sessionIdContext before
  falling back to the process-wide session.
- Refresh the 'latest' debug-log alias when the active writing session
  changes, so multi-session daemons don't leave it stale.
- Add regression tests for control-plane dispatch binding and alias
  refresh.

Relates to #9535.

* refactor(core): harden debug logger alias and ACP session context binding

- Gate AcpAgent.extMethod sessionIdContext.run on this.sessions.has()
  to avoid binding unsanitized caller-supplied strings.
- Serialize latest-debug-alias updates via a module-level promise chain
  and key the dedup marker by debug-directory + sessionId.
- Extract doUpdateLatestDebugLogAlias so marker/alias bookkeeping is
  centralized in updateLatestDebugLogAlias.
- Strengthen regression tests: assert the run callback actually
  dispatches, and add a negative case for alias dedup.

Relates to #9535.

* fix(cli): use Config session id for async-context binding and cover review findings

- Bind sessionIdContext to session.getConfig().getSessionId() in AcpAgent.extMethod
  so the context spelling matches Session.ts and avoids splitting legacy sessions
  across upper/lower-case debug log files (R3-1).
- Wrap top-level session handlers (cancel, setSessionMode, unstable_setSessionModel,
  setSessionConfigOption) with sessionIdContext.run (R2-4).
- Only bind extMethod dispatch for session-scoped methods, preventing global
  operations from being framed with a leaked session context (R4-1).
- Add regression tests for Config-spelling binding, global-method allowlist, and
  serialized latest-alias updates (R3-4).

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

---------

Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-24 05:51:24 +00:00
Yu Zhang
01416830c9
docs(vscode): correct minimum VS Code version (#9852)
Align the companion README requirement with the extension manifest so users do not try unsupported VS Code releases.

Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 05:20:29 +00:00
Yu Zhang
4d3f9ff571
fix(core): allow hook-bounced workflow reapproval (#9547)
* fix(core): allow hook-bounced workflow reapproval

* fix(core): release settled workflow approval sources

* fix(core): reject stale workflow approvals

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): retry failed approval delivery

Record approval incarnations only after synchronous event delivery succeeds, preserving retry recovery while pinning scheduler bounce identity.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): log approval delivery failures

Expose failed approval event delivery while preserving retry-on-update behavior and consolidate the shared approval test setup.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): stabilize approval event delivery

Share responder state across retries, reject stale incarnations, and bound automatic redelivery so transient listener failures cannot stall or duplicate approvals.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): isolate approval delivery per listener

Retrying a partial approval delivery re-emitted the event to every
listener, so a single failing listener both blocked healthy ones from
ever seeing the request and re-delivered duplicates to listeners that
already succeeded (forwardApproval/createApprovalHandler have no callId
dedup). Push the isolation down to the emitter: iterate rawListeners,
catch per listener, and retry only the listeners that actually threw.

Surface retry exhaustion via console.error so it is visible outside
debug sessions, and drop the dead awaitingByCallId guard (a same-tick
snapshot compared against itself could never differ). Cover
thrower-before-healthy, healthy-before-thrower, exhaustion reporting,
and both clearApprovalDeliveries teardown paths (abort and
all-tool-calls-complete).

---------

Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
2026-08-24 05:01:22 +00:00
jinye
014b903bf5
fix(daemon): Bound conditional-close refusal holds (#9820)
* fix(daemon): Bound active-work close refusal holds

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

* codex: address PR review feedback (#9820)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-24 05:00:40 +00:00
易良
3fd059368b
fix(core): invalidate token counts recorded for a switched model route (#9506)
* fix(core): invalidate token counts recorded for a switched model route

`/model` switches rebuild the content generator but keep the same
GeminiChat instance, so API-reported prompt/output token counts from the
previous route survived and anchored admission, output clamping, and
compression decisions for a different serialization (#9454).

Attribute the counts to the route that produced them
(Config.getModelRouteIdentity) and invalidate them on a route change so
all safety decisions fall back to the history-walk estimate.

Closes #9454

* fix(core): keep token counts scoped to producing route

* fix(core): close round-2 route-scoping findings (#9454)

- C1: client.test.ts session-token-limit gate test now seeds the chat
  mock's getLastPromptTokenCount (the gate's new source) instead of
  relying on the telemetry stub alone.
- C2: goal-turn-integration.test.ts passes the routeKey positional
  added to processStreamResponse and widens the local cast.
- S1: fix stale rationale comment — no session token-limit gate reads
  the telemetry mirror anymore; UI context counters and compression
  banners do.
- S2: pin tryCompress's entry invalidation with a focused test; manual
  /compress reaches it without sendMessageStream's entry reset.
- S3: direct config tests for getModelRouteIdentity — call stability,
  model@<sha-prefix> shape, and the guard keeping the registry baseUrl
  out of non-active model identities.
- R2-3: sendMessageStream now resolves the request route first and
  invalidates counts against IT (deriving the key from the actual model
  param on the non-exact branch), so an active-route count can no longer
  anchor an exact `\0` route's output clamp; regression test added.

* fix(core): close round-3 route-scoping findings (#9454)

- R3-1: hard-rescue rollback now restores tokenCountsRouteKey alongside the counts; tryCompress re-stamps the key to the active route mid-rescue, which left the resurrected request-route count riding the active key past the next entry invalidation (regression test: active-route read after failed rescue must not inherit the override count).

- R3-2(2): invalidation zeroes the telemetry cached-content mirror together with the prompt mirror so /context stops rendering a foreign cached count beside a zeroed prompt count; the mirror is documented as best-effort display state between a switch and the next guarded read (R3-2(1)).

- R3-4: collapse the requestRouteKey ternary into one getModelRouteIdentity call (the non-exact arm passed exactly the default parameter value).

- R3-5: resolve the active-route default lazily after the zero-count fast path instead of eagerly in the default parameter.

- R1-3: add the missing compressFast route-invalidation test (third entrypoint; mutation-verified).

* fix(core): preserve route-scoped token guards (#9454)

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

* fix(core): resolve full-turn route selectors at the session token limit gate (#9454)

* fix(core): stamp fallback-served token counts under the request route (#9454)

* fix(core): retain route-scoped token counts across route crossings (#9454)

* fix(core): keep route-scoped token counts consistent through compression (#9454)

* fix(core): keep rescued output counts and compression stamps route-scoped (#9454)

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-24 05:00:16 +00:00
易良
3d96e54641
chore(ci): refresh the core issue-owner pool (#9808) 2026-08-24 04:56:04 +00:00
C0d3N1nja97342
aac9606f78
fix(cli): skip terminal redraw optimizer on WSL/ConPTY (#7897)
* fix(cli): skip terminal redraw optimizer on WSL/ConPTY and enable sync output on Windows Terminal (#7634)

The streaming text repetition bug on WSL + Windows Terminal is caused by
the terminal redraw optimizer batching cursor-up sequences, which ConPTY
processes differently from individual per-line erases. The cursor lands
at the wrong row, causing each new frame to overlap remnants of the
previous one.

Two fixes:
1. Skip the redraw optimizer when WSL (WSL_DISTRO_NAME / WSL_INTEROP)
   or Windows Terminal (WT_SESSION) is detected, falling back to Ink's
   original per-line erase sequences that ConPTY handles correctly.
2. Enable synchronized output (DEC mode 2026) for Windows Terminal,
   which has supported it since v1.6, making frame updates atomic and
   masking any residual cursor positioning issues.

Fixes #7634

* fix: add WSL_INTEROP test and clear env vars in beforeEach to fix test fragility

Address review feedback on #7897:
- P1: clear WSL/Windows Terminal env vars in beforeEach so existing
  optimizer tests don't silently break when run inside WSL
- P2: add dedicated test for WSL_INTEROP detection

* review: address wenshao feedback on #7897

- Accept injectable env in installTerminalRedrawOptimizer (matches sibling
  terminalSupportsSynchronizedOutput), eliminating the need for
  beforeEach env-stubbing in test files
- Add QWEN_CODE_LEGACY_ERASE_LINES=0 as a force-on escape hatch for
  WSL/Windows Terminal users whose terminals handle the batched
  sequences correctly
- Collapse three near-identical WSL/WT skip tests into it.each
- Correct Windows Terminal DEC 2026 support version: v1.18, not v1.6
- Move WT_SESSION check above the TERM declaration in
  terminalSupportsSynchronizedOutput so the term isn't declared before
  its only consumer
- Add a table case asserting TMUX guard still wins over WT_SESSION
- Pass explicit empty env to installTerminalRedrawOptimizer in the
  synchronizedOutput composition test so it doesn't depend on the
  runner's environment

* fix(cli): narrow optimizer skip to WSL only, drop WT_SESSION

Per wenshao's review: WT_SESSION is set on the Windows side and is not
propagated into WSL shells without WSLENV, so it can never be the env
var that fires for #7634. Remove WT_SESSION from the optimizer skip
(WSL_DISTRO_NAME + WSL_INTEROP remain) and from the synchronized-output
allowlist. The synchronized-output change for Windows Terminal belongs
in its own PR once confirmed; bundling it into a WSL bug fix mixed two
independent behavior changes.

Also correct the comment: 'WSL or Windows Terminal' -> 'WSSL only',
and remove the WT_SESSION test cases from both test files.

* fix(cli): clean up WT_SESSION comment residue and pin its exclusion

Per review: the drop of the WT_SESSION skip left stale comments and no
test pinning the deliberate exclusion. Fix the force-enable comment
(WSL only, not Windows Terminal), complete the truncated WT_SESSION
rationale, and add a test asserting WT_SESSION alone does NOT trigger
the skip (it is not propagated into WSL shells). Also stub
QWEN_CODE_LEGACY_ERASE_LINES in beforeEach so the suite is isolated
from a host that has the flag set.

* refactor(cli): extract shared isWsl(env) into terminal-env util

WSL detection was inlined in terminalRedrawOptimizer (this PR) and
duplicated as a private helper in voice-availability. Extract a single
isWsl(env) into ui/utils/terminal-env.ts and use it from both sites so
the marker set cannot drift. Requested by maintainer in #7897 reviews.

* fix(ui): add license header and gate WSL_INTEROP in voice preflight

Round-3 review: terminal-env.ts shipped without the @license header
every sibling carries; and the voice-side isWsl migration was inert under
the test probe because voice-availability.test.ts only exercised the
WSL_DISTRO_NAME marker. Add the header and cover WSL_INTEROP via it.each. #7897

* docs(ui): note the separate core-side WSL check in terminal-env

Round-4 review: the extraction comment claimed the marker set cannot
drift, but ripgrepUtils.wslTimeout() in packages/core keeps its own
narrower WSL_INTEROP-only check because core cannot import from cli.
Document the exception so a future maintainer greps both sites. #7897

* docs(cli): document QWEN_CODE_LEGACY_ERASE_LINES escape hatch

Round-5 review (R5-1): isWsl(env) relies solely on env markers, which
env-scrubbing launchers (sudo, env -i) strip - so the #7634 skip never
fires in those contexts. Document the launch-time =1 fallback and note
it must be passed at launch because sudo drops the flag too. Also closes
the round-2 R2-3 gap (the flag was previously undocumented). #7897

* refactor(cli): move isWsl to core and apply maintainer review polish

wenshao's manual review suggested moving the shared WSL marker check to
packages/core so cli can import it (core cannot import from cli), while
ripgrepUtils.wslTimeout() keeps its deliberately narrower predicate. Also:

- Sharpen the ConPTY divergence comment with the concrete sequences the
  optimizer emits (CSI 1 B cursor-down, CSI n A multi-count) that Ink's
  native erase path never does.
- Replace the beforeEach vi.stubEnv test fixture with explicit empty-env
  arguments (truer 'not on WSL' fixture, no host-env dependency).
- Note the env parameter exists for testability.
- Trim the moved file's doc block to durable facts and tighten the docs
  row wording. #7897

* test(cli): pin the env default-parameter seam in redraw optimizer

Round-7 review: the production call path (installTerminalRedrawOptimizer
with no env arg) was never exercised - every test passed env explicitly,
so a mutation to the = process.env default (e.g. = {}) would pass green
while silently disabling the WSL skip and =1 escape hatch in production.
Add a hermetic test that stubs WSL_DISTRO_NAME and asserts the no-arg call
skips the optimizer. #7897

* test(cli): restore afterEach env cleanup for default-seam test

Round-8 review: placing vi.unstubAllEnvs() as the last statement in the
default-seam test body meant a failing expect (the exact regression the
test pins) would skip the cleanup and leak WSL_DISTRO_NAME=Ubuntu into
process.env for the rest of the file. Move the cleanup back into the
describe-level afterEach so it runs even on assertion failure. #7897

* test(cli): close the two minor coverage gaps from chiga0's review

Maintainer chiga0 approved the PR but noted two minor test gaps:

- The default-seam test only stubbed WSL_DISTRO_NAME, so a host-set
  QWEN_CODE_LEGACY_ERASE_LINES=1 would pass it for the wrong reason; stub
  the flag too so the assertion depends only on the WSL marker.
- The tri-state flag has no case for a non-standard truthy value; add one
  pinning that 'garbage' falls through to the platform default (WSL skip). #7897

* docs(cli): correct 'only path' claim in ConPTY divergence comment

Round-11 review: the comment asserted the optimizer is the ONLY path
emitting CSI 1 B / CSI n A, but the repo's patched ink build also emits
both sequence classes on its cursor-positioning path (buildCursorSuffix /
buildReturnToBottom, reachable via BaseTextInput.setCursorPosition).
Skipping the optimizer on WSL does not remove these from interactive
input. Narrow the claim to the per-frame erase-and-redraw path. #7897
2026-08-24 03:58:48 +00:00
callmeYe
a8b822f5d2
feat(web-shell): expose agent task changes (#9637)
* feat(web-shell): expose agent task changes

* fix(web-shell): deduplicate agent task callbacks

* fix(web-shell): ignore agent task telemetry churn

* fix(web-shell): skip immutable prompts in task fingerprint

* fix(web-shell): type agent task fingerprint
2026-08-24 03:54:49 +00:00
qqqys
5cff52c6e8
fix(goal): count catalog previews in the unit their budget is written in (#9835)
Evidence catalog previews were cut to 240 characters while the budget they
feed is 24,000 bytes. In UTF-8 those units differ by up to four times, so the
guard held only for ASCII. A legal 32-claim checkpoint of Chinese claims
serialized to roughly 29kB — over the cap on its own, before a single new
record had been scanned — which marked the window truncated, and `truncated`
switched compaction off. The one state compaction exists to resolve was the
one state it refused to run in, so the Goal was stopped as `usage_limited`,
the only status the reducer refuses to resume, with nothing left to salvage.
An English Goal never reached that state; a Chinese one could not avoid it.

Previews are now capped to 240 UTF-8 bytes on a code point boundary at the two
points a catalog entry is built. Nothing changes for ASCII, where the two units
already agreed; a CJK preview is shorter than before, which is the cost of the
cap actually holding. With it, a full checkpoint is bounded well inside the
catalog budget for every script, so a window can no longer start out truncated.

A truncated window now compresses rather than stopping the Goal. Overflow means
the budget is full and the newest evidence that did fit is exactly what a
checkpoint folds into claims; the older evidence left behind is already covered
by the previous checkpoint. Only a window that captured nothing at all has
nothing to salvage, and that is the sole remaining path to `usage_limited` here.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 03:27:52 +00:00
qqqys
674afdf1a7
feat(core): show what a workflow will do, and scope the grant that approves it (#9837)
A user approving a workflow was shown `Confirm WorkflowTool` over `Run a
workflow script (4127 chars)` — a character count standing in for arbitrary
model-authored JavaScript that can fan out to the per-run agent cap,
provision git worktrees and spend an uncapped token budget. The asymmetry
was visible inside one run: the subagent approvals that same workflow
bubbles up each got a full dialog.

Override getConfirmationDetails to show what the script says it will do,
read from `export const meta`: the workflow's name and description, its
declared phases, the resolved args, and a bounded excerpt of the source.
Meta is obtained through `extractAndStripMeta`, which parses rather than
evaluates, so nothing model-authored runs before the user has approved
anything. It throws on a malformed literal, so the call is wrapped: a
script with a broken meta block stays approvable-or-rejectable rather than
taking the dialog down with it.

Everything displayed goes through `stripAnsiAndControl` first. The screen
ships with the preview rather than after it, because until now nothing was
displayed and so nothing could be spoofed — a preview without it is what
would open the hole. Single-line fields are flattened, which is what we
want for a `meta.name` spanning three lines. The script excerpt is
sanitized per line instead: `\n` is a C0 control character, so the naive
call would collapse the script into one unreadable line.

Scope the grant on the same object. An inline `script` is fresh source
every time, so it can never be pre-approved: `hideAlwaysAllow` removes the
option, and an empty `permissionRules` stops `injectPermissionRulesIfMissing`
from supplying the bare tool name, which `buildPermissionRules` documents as
matching every invocation of the tool. A `scriptPath` names a file the user
chose, so it stays pre-approvable but scoped to that path via a `key:value`
param matcher. The rule is built with the same helpers the matcher uses, and
the test asserts it behaviourally -- a rule that reads plausibly but never
matches would make "always allow" silently do nothing.

Finally, move the token-cost warning ahead of the spend it warns about. It
previously appeared only on the success path, i.e. after the run, and never
at all when the run failed.


Claude-Session: https://claude.ai/code/session_01MDuRWMhKbqEYZXbbLhn3en

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 03:27:38 +00:00
Tianyuan
a2e458deef
feat(auth): add Kimi (Moonshot AI) as a built-in third-party provider (#9814)
Adds a Moonshot preset to the /auth Third-party Providers menu, offering
the international and China API endpoints and seeding the current Kimi
model catalog. Moonshot speaks the OpenAI protocol, so this is a
declarative preset with no new mechanism and no change to the provider
type.

Model metadata follows Moonshot's published capabilities. K3 is marked
thinking-mandatory: its API exposes a reasoning-effort knob but no way to
turn thinking off, so a disable shape must never reach the wire. The two
code models and K2.6 keep thinking toggleable, and all four accept image
and video input, which the K2.6 guide states explicitly.

Registers the new credential env key everywhere a provider key has to
appear: the no-AK CI gate and its pinned assertion list, and the
telemetry provider mapping, both by env key and by request hostname so
Kimi traffic is attributed rather than reported as unknown. The three
first-run docs that enumerate built-in providers are brought back into
agreement, which also picks up entries that were already stale.

Closes #9197
2026-08-24 03:18:40 +00:00
ytahdn
747dbf00c2
feat(web-shell): compact agent activity summaries (#9657)
* feat(web-shell): compact agent activity summaries

* fix(web-shell): preserve merged todo timing

* fix(web-shell): address compact summary review

* test(web-shell): cover compact review edge cases

* fix(web-shell): make the folded thought header row fully clickable

* fix(web-shell): guard folded thought toggle against popover clicks (#9657)

* test(web-shell): cover thinking header hit area and parallel-agent thoughts (#9657)

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-24 02:46:07 +00:00
Shaojin Wen
03bcfe44b5
refactor(review): split SKILL.md into a core body plus verdict-gated reference files (#9804)
* refactor(review): split SKILL.md into core body plus verdict-gated references (#9787)

The bundled review skill's SKILL.md (381,984 bytes, ~95k tokens) was
injected whole on every run, although large stretches are conditional
territory a given run never touches. Split it into a core body plus
reference files the orchestrator reads on demand, gated on the parse-args
verdict it already holds:

- references/posting.md — whole Step 7 (authorisation gate, presubmit,
  anchors, submit, 422/head-drift recovery, publish-assets). Loaded only
  when posting is live (comment.effective or a same-session post request;
  PR + high only). Its compose-state field list relocates verbatim to
  Step 6's Verdict section, because a report-only run still writes that
  state for compose-review without ever loading posting.md.
- references/persistence.md — whole Step 8 (tail batching, report,
  artifact registration, incremental cache). Loaded before Step 8 on every
  run except cross-repo lightweight mode.
- references/aone.md — the self-contained Aone blocks of Step 1 (clone and
  two-host rules, a1-backed surface, the five submit failure shapes, dedup
  shape notes). Loaded before match-remote when the host/meta says Aone.

The split moves whole steps; incident-backed rules stay with the step they
guard. The write prohibition and the posting gates remain in the injected
core so they bind runs that never load a file. No enterprise.md: the GHE
host notes are sentences woven into universal paragraphs, and extracting
them would strip rules from steps that remain in core.

Injected prompt: 381,125 -> 304,427 body bytes. Typical non-posting runs
(local/file/PR, any effort) save ~58 KB (~15%); lightweight runs ~77 KB
(~20%); posting runs load posting.md back and save only the Aone block.
The issue's "roughly a third" estimate is unreachable under its own
whole-step guardrail — Steps 1 and 6 dominate the core and interleaving
forbids fragmenting them; Step 5 / Step 3C effort-gated splits are the
natural follow-up.

Drive-by, verified against #9627's revert-guard test and the a1
implementation: three stale sentences still claiming comment-status "has
no Aone backing" are aligned with the a1-backed behavior that landed in
#9627.

Tests: SKILL.test.ts revert guards now govern the full corpus (SKILL.md +
references), with new pins for the gates, the core-retained invariants and
the no-duplication invariant; run-skill-parity reads the corpus oracle;
bundled-skills integration pins the shipped reference files. Verified by
build + bundle, all review-skill suites, and a real-model E2E run of the
split skill (verdict-gated reads observed: persistence.md loaded before
Step 8, posting.md and aone.md correctly skipped).

* fix(review): drop uninterpolated template tokens from skill references (#9804)

* test(review): guard stems oracle by persistence.md, pin gate clauses to bullets (#9804)

* fix(review): close Step 7 reference doc gaps from reverse audit (#9804)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-24 02:41:49 +00:00
ShiZai
a7128d30c3
fix(core): isolate fork cache readers by session (#9471)
* fix(core): isolate fork cache readers by session

* fix(core): skip foreign cache extraction

* perf(core): skip foreign cache before memory IO

* test(core): provide session id in extraction fixture

---------

Co-authored-by: tomsen-ai <230283659+tomsen-ai@users.noreply.github.com>
2026-08-24 02:39:59 +00:00
Yu Zhang
deed96e733
fix(core): avoid ghost shutdown pending state (#9550)
* fix(core): avoid ghost shutdown pending state

* fix(core): gate assignments during shutdown delivery

Keep teammates out of task assignment while shutdown delivery is pending, with rollback on mailbox failure.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): preserve concurrent shutdown state

Track in-flight and delivered shutdown requests independently so a failed retry cannot reopen assignment gates owned by another request.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): settle resolved shutdown writes

Keep response resolution separate from mailbox delivery so late writes cannot re-arm a completed shutdown generation.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): separate shutdown marker ownership

Track delivered requests independently from test-only markers so failed writes clean up without dropping pending successful deliveries.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): track shutdown response ownership

Represent delivered shutdown requests as reservable tokens so concurrent writes and responses conserve pending state without stale generations or duplicate aborts.

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

* fix(core): deliver every stacked shutdown request

A busy teammate that received two shutdown requests only ever saw the
first: one idle flush read all unread shutdown messages but enqueued
only shutdowns[0], leaving the rest marked-read and dropped. Each write
still minted a delivered token, so the un-delivered request's token
could never be consumed by a response and hasShutdownWork stayed true
forever, permanently gating task assignment for a live teammate.

Enqueue every shutdown returned by a single flush so token count and
delivered-request count stay balanced. Add coverage for stacked
delivery, markShutdownRequested idempotence, the responsesInFlight
gating window, and abort-on-approve when a MESSAGE_SENT listener throws.
Extract the repeated gated-mailbox-write scaffolding into one helper and
reset the shared mailbox mocks per test.

* chore(core): log shutdown ledger state transitions

Add debug logging at the shutdown state-machine transitions (token
mint, response settlement, ledger drain) so a stuck shutdown-pending
teammate can be diagnosed from logs without reading private fields.

Addresses reviewer suggestion on #9550.

---------

Co-authored-by: zhangyu.34 <zhangyu.34@bytedance.com>
2026-08-24 02:36:23 +00:00
qqqys
75fb40d832
refactor(goal): render Goal continuation prompts from one core renderer (#9581)
* refactor(goal): render Goal continuation prompts from one core renderer

The prompt sent when `runtime.finishTurn` schedules another Goal turn was
assembled independently in three hosts: the TUI's inline array in
`useGeminiStream`, and a `buildGoalContinuationParts` in each of the ACP
session and the non-interactive CLI. Three copies of the same four shared
lines have already drifted -- the TUI carries the anti-spoofing guard lines
but no objective, while ACP and non-interactive carry the runtime
continuation context but no guard lines.

Upcoming work adds further variants (an "objective was edited" announcement
and a budget wind-down prompt). With the text living in three places, every
new variant means three edits, which is precisely how the current drift was
produced. This moves assembly into `packages/core/src/goals/goal-continuation-prompt.ts`,
where a variant is a case in one function and the shared prefix exists once.
The two `buildGoalContinuationParts` helpers keep their names and signatures
and simply delegate.

This is a pure refactor: no prompt text changes. Each host still emits a
byte-identical string to the one it emitted before. The existing drift is
preserved deliberately and is left for a separate, behavior-changing
follow-up. The new unit test pins the complete rendered string for both
variants with and without verifier feedback, so any future edit to a line
surfaces as a test diff; the existing host tests pass unmodified.

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

* fix(goal): tighten continuation renderer contract

* test(goal): cover verifier feedback hosts

* refactor(goal): hoist Goal continuation parts builder into core (#9581)

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-24 02:36:02 +00:00
易良
00461ab275
fix(core): mark agent launch failures as failed tool calls (#9519)
* fix(core): mark agent launch failures as failed tool calls

The subagent-not-found path and failWorktreeProvisioning() returned a
ToolResult with llmContent and a failed returnDisplay but no error
field. The scheduler records a failure only when error is set
(coreToolScheduler keys off toolResult.error), so a launch that never
ran was counted as a successful agent call: no error-formatted model
response, no failure-path hooks, and optimistic success telemetry.

Set error consistently with buildSpawnBlockedResult in the same file,
which already documents this convention. The full guidance text (the
available-subagents list, the specific provisioning reason) goes into
error.message because the failure path forwards only that to the
model. returnDisplay is unchanged, so terminal output is identical.

Fixes #9509

* fix(core): mark agent launch failures

* test(core): cover preserved agent worktree errors

* test(core): keep preserved worktree error test foreground

* test(core): fail subagent via creation seam in preserved-worktree test
2026-08-24 02:34:49 +00:00
Shaojin Wen
20e076f8f3
fix(core): surface nested sub-agent approvals under background parents (#9793)
* fix(core): surface nested sub-agent approvals under background parents

A tool call needing confirmation inside a nested sub-agent (launched by a
background agent or fork) was neither surfaced nor denied: the prompt-
avoidance policy was stamped on an Object.create wrapper while the rebuilt
tool registry binds to agentConfig, so nested schedulers resolved
Config.prototype's false and believed they could prompt; and the nested
invocation's emitter was never bridged, so TOOL_WAITING_APPROVAL fired with
no listener and the call waited forever - the enclosing agent hung silently.

- Stamp getShouldAvoidPermissionPrompts on agentConfig itself (launch and
  resume paths) so nested launches inherit the real policy through their
  config prototype chains: hang-forever becomes an explicit deny when
  bubbling is off.
- Bridge nested foreground launches' approval events onto the nearest
  backgrounded running ancestor's Background-tasks entry (walking the
  registry's parentAgentId lineage), so they park where the ancestor's own
  approvals go and the user can answer them from the dialog.
- Mark bridged approvals with the nested runtime's subagentId (declared by
  the bridge caller via nestedSource - runtime ids and registry ids use
  different suffixes so comparing them cannot work) and show the waiter in
  the Background tasks dialog.

Fixes #9782

* chore(i18n): add 'from nested agent' locale entries

* fix(core): harden nested sub-agent approval bridging (#9793)

* fix(core): tighten nested approval bridging per review round 2 (#9793)

- addPendingApproval returns a discriminated result ('parked' |
  'duplicate' | 'unavailable') mirroring the workflow registry's
  parkPendingApproval, so the bridge drops re-emitted events without
  re-running the dedup scan on the caller side
- log a debug line when a re-emitted approval event is dropped, so
  "approval never appeared" sessions can tell a drop from a lost event
- extract stampBackgroundPromptPolicy next to createApprovalModeOverride;
  both the launch and resume paths now share one stamp + rationale
- gate the nested approval bridge on the inherited prompt-avoidance
  policy like the sibling bridges, instead of wiring a dead subscription
  under auto-denying ancestors
- refresh two JSDoc examples that still cited the deleted
  Object.create wrapper

* fix(core): key parked background approvals on runtime identity (#9793)

Generated tool-call ids (`call_qwen_N`) are only unique per conversation,
so two nested sub-agents whose first id-less call needs confirmation both
arrive under the same callId on their shared background ancestor entry.
The parked-approval queue keyed identity on callId alone: the second
runtime's prompt was dropped as a "duplicate" with nothing in the dialog
to answer, and a resolution or TOOL_RESULT for one runtime could clear
another runtime's parked prompt.

Key identity on (subagentId, callId) — the same composite the workflow
run registry already uses — across addPendingApproval's duplicate
predicate, resolvePendingApproval, and clearPendingApproval. Own
approvals stay unstamped, so same-call re-emissions from the entry's own
runtime still dedupe while different runtimes never collide.

* fix(core): attribute nested approval logs and pin composite-key dedup (#9793)

* fix(core): attribute remaining approval failures and pin own-vs-nested collision tests (#9793)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-24 02:31:45 +00:00