Commit graph

3377 commits

Author SHA1 Message Date
易良
647500caf6
fix(core): catch content-only thinking-tag leaks on all OpenAI-compatible providers (#8818)
* fix(core): catch content-only thinking-tag leaks on all OpenAI-compatible providers

Production captures (issue #6666) show hybrid-thinking models occasionally
bypassing the reasoning channel and emitting their thinking as literal
<think>/<thinking> text inside content. The content-only leak fallback only
covered the DashScope provider, and its 128-char candidate cap released
confirmed opening tags mid-stream, so real-world leaks (always longer than
the cap, typically unclosed) still reached users.

- Enable contentOnlyThinkingTagLeaks on DefaultOpenAICompatibleProvider so
  every OpenAI-compatible endpoint gets the conservative fallback (gated to
  turns that start with a thinking tag and carry no structured reasoning or
  prior visible content); drop the now-redundant DashScope override.
- Classify an opening tag followed by content with no balancing closing tag
  as an unclosed thinking block: held mid-stream, rejected as
  PROTOCOL_TAG_LEAK at stream end. Whitespace-only tails stay undecided.
- Exempt confirmed opening tags with real content from the length-cap release
  so long unclosed blocks are rejected instead of leaked.

Adds regression tests replaying the sanitized production shape (red on the
previous code) plus a control proving the provider gate.

* fix(core): document the fail-closed trade-off and cover the over-cap throw

Review follow-up: state honestly that a legitimate balanced literal longer
than the candidate cap whose closing tag has not arrived yet is rejected
along with real leaks (indistinguishable at that point), and add a
regression test for the mid-stream fail-closed throw on over-cap confirmed
opening tags.

* fix(core): parse closed thinking tags by default

* test(core): update DashScope parsing-options assertion after override removal

The vendor-specific getResponseParsingOptions override was removed in
the parent commit so DashScope inherits the default provider's options,
which now include taggedThinkingTags alongside contentOnlyThinkingTagLeaks.

* fix(core): reject nested unclosed thinking blocks

* fix(core): handle nested thinking tags

* fix(core): preserve generic thinking-tag compatibility

* fix(core): defer thinking leak rejection until finish

* fix(core): note streaming-only scope of thinking-leak defense
2026-08-12 06:30:17 +00:00
jinye
9259c35500
feat(serve): Propagate session list cancellation (#8954)
* feat(serve): Propagate session list cancellation

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

* codex: address PR review feedback (#8954)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 05:03:19 +00:00
Harjoth Khara
c2af99f169
fix(core): recognize OpenAI SDK APIUserAbortError as an abort (#8399)
* fix(core): recognize OpenAI SDK APIUserAbortError as an abort

The OpenAI SDK is the request path for `auth_type=openai` — the most common provider here. When a user cancels an in-flight request the SDK throws `APIUserAbortError`, but `isAbortError` only matched `.name === 'AbortError'` or the Node `ABORT_ERR` code. `APIUserAbortError` sets neither (its `.name` stays 'Error'), so a user cancel was not recognized as an abort.

The consequences flow through the two callers that gate on `isAbortError`: the cancel is logged/telemetered as an `api_error` instead of suppressed, and the retry classifier labels it `'unknown'` instead of `'abort'`, so it misses the authoritative no-retry short-circuit.

Recognize `APIUserAbortError` by its class name (preserved by the build's `keepNames`), keeping this provider-agnostic util free of an SDK import.

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

* test(core): pin the abort match against sibling SDK errors

Review follow-up on #8399.

The constructor-name match was pinned only on its positive side, so broadening it (for example to any `API*` class) would have kept the whole suite green while turning transient `APIConnectionError` failures into "user cancelled" — stopping retries and mislabelling them. Add a negative test with `APIConnectionError`; verified it fails against that exact broadened match and passes against the real one.

Also pin the Anthropic path. Both SDKs this package depends on are Stainless-generated and share the `APIUserAbortError` class name, so one check already covers `auth_type=anthropic` — now asserted by a test and stated in the comment, rather than left as an undocumented coincidence.

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

* fix(core): suppress the api_error telemetry event on user cancels

@wenshao and @yiliang114 verified the isAbortError fix but showed the
api_error the reporter saw is not gated by it: it comes from
LoggingContentGenerator.safelyLogApiError, which emitted ApiErrorEvent
unconditionally. So a user cancel still produced a qwen-code.api_error event
(error_type APIUserAbortError) after the util-level fix.

Gate safelyLogApiError — when the caller's signal is aborted and the error is
abort-shaped, skip the event; the span already records the cancellation via its
aborted status, so the signal isn't lost. Thread the abort signal through the
three call sites. Adds a caller-level regression test asserting no api_error
fires on a user cancel, plus a contrast test that a real failure still reports.

Also from the review: extend the isAbortError JSDoc for the third shape, scope
the keepNames comment to the CLI bundle (vscode-ide-companion minifies without
keepNames), and assert `.not.toBe('AbortError')` rather than the brittle
SDK-internal `.name === 'Error'`.

Refs: #8398

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

* test(core): pin the cancel gate's truth table and its stream call site

Review follow-up on #8399.

The gate needs both conjuncts, an aborted caller signal and an abort-shaped
error, but the suite only pinned the two cases where they agree. Either
conjunct could be dropped and the suite stayed green. Add the two
disagreeing cases: an abort-shaped error the user never caused, where the
signal never fired, must still report; and a genuine failure that races a
cancel must still report. Each one fails against exactly its own
single-conjunct mutant.

The gate was also only exercised through the non-stream path, while
cancelling part-way through a response is the common case and surfaces at a
different call site inside the iteration. Add a stream test that yields a
chunk, aborts, then throws. It fails when the signal is dropped at that call
site.

Refs: #8398

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

* test(core): pin the setup and DOMException cancel paths, correct a rationale

Review follow-up on #8399, round 5.

A user cancel can reach the telemetry gate at three call sites, but only two
were pinned. The stream-setup site was untested: cancelling before any chunk
exists rejects the SDK create() call rather than the iterator, and dropping
the signal argument there left the whole file green. Add a test for it; it is
the only one that fails under that mutation.

The mid-stream test's rationale was also wrong about which error shape gets
there. The pinned openai SDK swallows a mid-stream abort and ends the
iterator normally (core/streaming.mjs: `if (isAbortError(e)) return;`), so
its APIUserAbortError never reaches that catch. The shape that does is the
DOMException-style AbortError from the @google/genai path, whose SSE reader
has no abort special-case. That branch was unpinned: narrowing the gate to
APIUserAbortError alone kept the existing mid-stream test green. Add the
DOMException variant, which is the only test that fails under that
narrowing, and correct the comment.

Tests only; no production change.

Refs: #8398

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

* fix(core): keep reporting timed-out side queries, not just user cancels

Review follow-up on #8399, round 6.

The cancel gate added in ad700bf5 keyed on whether the caller's signal had
aborted, never on who fired it. Internal side queries compose a deadline into
the same config.abortSignal a user cancel travels on: memory recall (30s),
forget (8s) and the arena summary all route AbortSignal.timeout through
baseLlmClient into the request. When such a budget runs out the provider SDK
still rejects abort-shaped, so the gate read it as a user cancel and dropped
the api_error event. Background LLM work could then fail silently behind a
clean model-health chart -- the inverse of the misreporting this PR exists to
fix, and a regression against the behaviour before that commit.

Add isUserCancel(error, signal), which excludes aborts whose reason is a
TimeoutError. Node sets signal.reason to a DOMException named TimeoutError for
AbortSignal.timeout, and AbortSignal.any adopts the firing source's reason, so
the reason separates a real cancel from an expired budget after composition.

Route both suppression sites through it. shouldSuppressErrorLogging carried
its own copy of the same conjunction; sharing one predicate is the actual fix
for the divergence class behind #8356, where one path was gated and the other
was not. Its existing comment already promised not to suppress aborts the user
should know about -- that promise now holds for timeouts too. The auth-only
override in qwenContentGenerator is deliberate and left alone.

Tests: the timeout case at both layers, the remaining truth-table cell (a
request with no signal must still report), and unit coverage for isUserCancel
including both AbortSignal.any directions. Removing the timeout exclusion
fails three of them; treating a missing signal as aborted fails two and
nothing else.

Refs: #8398

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

* fix(core): signal internal deadlines as TimeoutError so they stay reported

Review follow-up on #8399, round 7.

The cancel gate distinguishes a user cancel from an internal deadline by the
abort reason, but only AbortSignal.timeout produces a TimeoutError reason.
Deadlines built from a plain AbortController plus setTimeout carry other
shapes, and all four of them reach a model request:

  goalHook            bare abort()          -> reason 'AbortError'
  goal-verifier       new Error(...)        -> reason name 'Error'
  promptHookRunner    bare abort()          -> reason 'AbortError'
  workflow-stall      abort('stalled')      -> reason is a string

Each of those reads as a user cancel, so the api_error for a timed-out
background query was suppressed and the work failed behind a clean
model-health chart. Before the gate landed these were emitted.

A bare abort is signal-level indistinguishable from a user pressing Esc, so
no predicate can separate them -- the fix belongs at the producers.
isUserCancel is unchanged. Each deadline now aborts with a TimeoutError-named
DOMException, matching combineAbortSignals in utils/abortController.ts. The
stall watchdog keeps its 'stalled' text as the DOMException message, now
exported as STALL_ABORT_REASON; no production code compared that reason by
equality, only its test.

Also: two call-site tests for shouldSuppressErrorLogging, whose two behaviour
changes had no coverage, and a debug line on the gate's early return so a
suppressed error leaves a trace instead of vanishing from telemetry and the
debug log at once.

Reverting all four producers left the whole suite green before this commit;
each is now pinned by exactly one test.

Refs: #8398

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

* fix(core): share the timeout-abort contract and convert its fifth producer

Review follow-up on #8399, round 8.

The wall-clock cap in workflow-sandbox was a fifth internal deadline firing
a bare abort. Its signal reaches dispatched subagents' model requests, so a
workflow killed at its 30-minute budget read downstream as a user cancel and
its api_error was suppressed. It now aborts TimeoutError-shaped like the
other four; user cancels via handle.abort() stay bare, so first-fire-wins
keeps the cases separated.

That recurrence is the argument for a shared symbol: the TimeoutError shape
was hand-rolled at every site, so the next budget written with a bare
abort() compiles cleanly and silently re-creates the bug. Add
timeoutAbortReason() next to isUserCancel — producer and consumer of the
contract in one file — and convert all five sites to it.

Compose the Qwen generator's shouldSuppressErrorLogging with super instead
of replacing it. Pre-PR both families agreed (both logged cancels); fixing
only the base class made them disagree, so a cancel under qwen OAuth logged
as an API error while the identical cancel under openai auth was suppressed.
The test-file mock of the base class now delegates to the real isUserCancel
so composition is testable.

Also: the four doc comments still teaching abort('stalled'), and the missing
isUserCancel truth-table cell (non-abort error on a timeout signal), which
kills the `return !isAbortError(error)` mutant.

Reverting the sandbox producer or the Qwen composition each fails exactly
one new test.

Note: src/tools/read-file.test.ts and zoom-image.test.ts each have one
failure on this branch that reproduces on the unmodified head — pre-existing
from the latest merge of main, unrelated to this change.

Refs: #8398

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

* fix(cli): signal the voice-refine deadline as TimeoutError

Review follow-up on #8399, round 9.

The voice transcript refinement caps its side query at 2.5s with a bare
timer abort, and that signal reaches a model request through runSideQuery.
A hung fast model therefore read downstream as a user cancel and its
api_error was suppressed. The deadline now signals TimeoutError via
timeoutAbortReason (already exported from core); the external-abort path
stays bare, since forwarding the caller's cancel is exactly a user cancel.
Reverting to the bare abort fails the extended timeout test; a sibling test
pins that the user-cancel path keeps its AbortError shape.

With this the audit of AbortController-plus-timer deadlines on model-request
paths is complete. Two timer aborts remain bare deliberately: the ACP
recovered-parent wait (event subscription, no model request) and runBudget's
budget stop, which is a user-configured planned interruption of a healthy
request — reporting it as an api_error would put noise on the model-health
chart for an intentional stop.

Also from this round: a test pinning the daemon/ACP string-reason cancel
('qwen:user-cancel', the configuration #8356 was reported from) so reason
discrimination stays negative-only; the suppression trace now carries
model, prompt_id, error type and reason name, so the only debug-side
evidence of a suppression identifies its request; and the isAbortError
docstring no longer calls the class "user cancellation" — the SDKs throw
it for any aborted signal, and callers wanting user intent are pointed at
isUserCancel.

Refs: #8398

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

* fix(core): signal the goal checkpoint verifier deadline as TimeoutError

Review follow-up on #8399, round 10.

The goal checkpoint verifier is a sibling of goal-verifier with the same
30s deadline and the same plain-Error abort reason that goal-verifier had
before round 7 -- so its timeouts read downstream as user cancels and the
gates this PR adds suppressed its api_error and debug log. Convert it the
same way, via timeoutAbortReason, and extend its existing timeout test with
the reason-shape assertion; reverting the conversion fails exactly that
test.

This corrects the round-9 audit claim: that sweep matched bare abort()
calls and missed this site because it aborts with an argument of the wrong
shape. Re-swept for abort(new Error and abort(' string reasons on timer
paths; no further instance reaches a model request.

Refs: #8398

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

* docs(core): design note for the user-cancel vs internal-deadline contract

Add docs/design for the TimeoutError-reason invariant this PR introduces:
why abort-shaped is not a proxy for user intent, the isUserCancel /
timeoutAbortReason split, the converted producers and the two deliberately
bare aborts, and the honest limits — the invariant is convention not a type,
and broadening isAbortError touches every consumer, not just the two refined
gates. States plainly that this does not fix #8356's transcript blackout.

Also soften two code comments that asserted #8356 was *caused by* the
logging-path divergence. The issue does not establish that; the divergence
is what #8398 fixes. Point the comments at #8398 instead.

Refs: #8398

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

* docs(core): record the daemon prompt deadline as an open boundary case

Round-11 review (R11-1) found the producer audit's completeness claim was
false: the daemon prompt deadline reaches a model request laundered as the
'qwen:user-cancel' string (reason re-stamped at the Session admission boundary,
after the cause is dropped at the ACP wire), so isUserCancel reads it as a
cancel and suppresses its provider-health api_error.

Stop the design note claiming the negative-only invariant is self-enforcing,
and document this producer honestly: the deadline is still surfaced via the
prompt_deadline_exceeded terminal and an errored LLM span, so only the
llmApiErrors count is affected; whether that count should include a
caller-configured local deadline is a semantic question (llmApiErrors is
documented provider-side) left for a maintainer, with the cross-boundary
_meta fix noted if it is ruled a regression.

No behavior change; this commit is documentation only.

Refs: #8398

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

* refactor(core): reduce #8399 to the #8398 fix, split out the invariant

Per @wenshao's review: land the bug fix here, move the cross-cutting
"internal deadlines must signal TimeoutError" invariant to its own PR so its
polarity (default-suppress vs default-report) can be decided before it merges.

This PR now contains only:
- isAbortError recognizes the OpenAI SDK's APIUserAbortError (the #8398 fix).
- safelyLogApiError skips the api_error event on a user cancel, using the
  approximately-correct gate `abortSignal?.aborted && isAbortError(error)`.
  This stops the #8398 noise immediately. Internal timeouts remain suppressed
  under this gate — that is the status quo, not a regression.

Removed and deferred to the follow-up PR: isUserCancel, timeoutAbortReason,
the seven producer conversions (goals, hooks, workflow, voice), the Qwen
override composition, and the design note.

Refs: #8398

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-12 03:16:33 +00:00
Dinesh Palanisamy
b62164cc29
fix(core): accept dotted-minor Claude aliases and add Opus 5 to token limits (#8585)
* fix(core): accept dotted-minor Claude aliases and add Opus 5 to token limits

Anthropic model-ID parsing in the harness assumed hyphenated minors
(`claude-opus-4-8`) and hardcoded the Opus 4.6-4.8 minor set in three
token-limit regex. Two things fall out of that:

1. LiteLLM/Vertex/Bedrock-style proxies commonly expose Anthropic Model
   Groups with dotted minor versions (`claude-opus-4.8`). The parser
   silently degrades these to `{major, minor:0}`, disabling adaptive
   thinking, the 4.8+ temperature-drop gate, and the 4.6+ effort tiers;
   the first send takes a hard 400 from the server on
   `thinking.type.enabled`. The normalize() step compounds this on the
   token-limit side by stripping `-4.8` as a version tag and collapsing
   the id to `claude-opus`, losing the 1M/128K carve-outs.

2. Opus 5 (bare major or `-5-0`/`-5-1`) is already fully handled by the
   algorithmic adaptive-thinking gates but falls through the hardcoded
   token-limit regex, losing 1M input, 128K output, and the
   defaultOutputCeiling exemption.

Fix:
- `parseClaudeModelVersion` accepts `[-.]` as the minor separator.
- `normalize()` in both core and the vscode-ide-companion mirror rewrites
  dotted-minor Claude aliases to hyphenated form up-front, before the
  trailing-suffix strip eats them.
- Broaden the three Anthropic token-limit regex to include Opus 5:
  `/^claude-opus-(?:4-(?:6|7|8)|5)/`.
- Bring the vscode-ide-companion input-side mirror up to core parity for
  Opus 4.6-4.8 + Opus 5 (it was previously stale on 4-6 only).

Regression tests cover dotted-minor aliases, Opus 5 (bare + numbered +
dotted), and reseller-prefixed forms (`vertex/`, `bedrock/`).

* fix(core): make the Claude alias rewrite family-agnostic and de-duplicate the Opus window regex

Addresses the review round on #8585.

- The dotted-minor rewrite hardcoded the Claude family list, forking
  CLAUDE_MODEL_FAMILIES (documented as the single source of truth) into two
  more copies. Match the family segment as `claude-[a-z]+` instead, so a
  sixth family can never silently regress to the generic 200K/64K window.
  Verified: `claude-newfam-5.1` now normalizes to `claude-newfam-5-1`
  instead of collapsing to `claude-newfam`.
- The rewrite consumed only one dotted component, so `claude-opus-4.8.0`
  and `claude-opus-4-8.0` still collapsed to `claude-opus-4` while
  parseClaudeModelVersion accepted them -- the two subsystems disagreed
  about the same id. Broadened to an optional hyphenated minor plus any
  further dotted components.
- The rewrite is hyphen-anchored and ran BEFORE the whitespace collapse, so
  space-separated display names (`Claude Opus 4.8`) skipped it entirely and
  were capped at 200K/64K on a 1M/128K model group. Moved the collapse
  above the rewrite; safe because the rewrite is anchored on `^claude-` and
  is a no-op for every other family.
- The policy regex existed verbatim five times across two packages, and
  defaultOutputCeiling's independent copy could silently disagree with the
  pattern tables and halve real output budgets. Extracted
  CLAUDE_OPUS_EXTENDED for the three core sites and a mirror-local constant
  for the two companion sites.
- The companion mirror emitted LIMITS['128k'] (131072) where core emits the
  vendor-declared 128_000, a 3072-token divergence between two files whose
  header mandates they stay in sync. Corrected to 128_000.
- Documented in the mirror's header that the companion's live context-limit
  path resolves through core's knownTokenLimit, so the copy isn't mistaken
  for the shipping path.

Tests: added 4 core cases and a new 11-case collocated test for the
companion mirror, which previously had none despite three behavior changes
landing in it. Each behavior fix was confirmed to fail without its change.
89 core / 11 companion / 229 generator tests pass; no new type errors
(vscode-ide-companion's 108 pre-existing errors are byte-identical before
and after).

---------

Co-authored-by: Palanisamy, Dinesh <Dinesh.Palanisamy@netapp.com>
2026-08-12 03:15:56 +00:00
Balachandar Ramakrishnan
5e97fc8f2e
fix(core): align mock workspace path containment (#8759)
* fix(core): align mock workspace path containment

* fix(core): reject symlink cycles in workspace mock

* test(core): cover workspace mock path parity

* fix(core): share workspace path resolution semantics

* Update packages/core/src/test-utils/mockWorkspaceContext.test.ts

Co-authored-by: jinye <djy1989418@126.com>

* Update packages/core/src/utils/workspaceContext.ts

Co-authored-by: jinye <djy1989418@126.com>

* fix(core): harden workspace path regression tests

* test(core): isolate workspace mock filesystem stubs

* Update packages/core/src/utils/workspaceContext.ts

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

* fix(core): preserve symlinked missing path ancestry

* refactor(core): centralize missing path error checks

---------

Co-authored-by: jinye <djy1989418@126.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-12 02:55:09 +00:00
Nothing Chan
de470b95b4
feat(telemetry): align session lifecycle with OpenTelemetry (#8616)
* fix(telemetry): align session lifecycle with OTel

* feat(telemetry): complete session lifecycle coverage

* fix(telemetry): deduplicate deferred session starts

* test(telemetry): cover duplicate session starts

* fix(serve): emit daemon session starts

* fix(telemetry): repair session lifecycle test wiring and record attributes (#8616)

Restore the missing logSessionEnd export in the config-session-env mock
(the Test-check failure), add event.timestamp to the session lifecycle
records to match every sibling emitter, and pin the previously untested
wiring: end-before-start ordering and the /clear non-continuation rule at
the Config level, the deferred-init catch-up guard, the shutdown emission,
and the loggers->session-events link. Correct the telemetry docs claims
about session.previous_id, end_session, and the log event catalog.

* fix(telemetry): skip session lifecycle transition on same-id resume (#8616)

* test(telemetry): pin session lifecycle behaviors per review (#8616)

Mutation testing in review round 4 showed three properties survived the
whole suite unguarded: the session-start guard resetting on session.end,
the daemon runtime config's isTelemetryInitializationDeferred flag, and
the catch-up session.start emitting only after NodeSDK.start(). Add
focused assertions for each, and replace a duplicated config mock literal
with the makeFakeConfig factory.

* fix(telemetry): emit session start catch-up on every init path (#8616)

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

---------

Co-authored-by: zjunothing <zjunothing@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>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-12 02:53:06 +00:00
Dragon
ac78acd3c5
fix(core): resolve Qwen 3.8 reasoning budget conflicts (#8525)
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
SDK Python / Classify PR (push) Waiting to run
SDK Python / SDK Python (3.10) (push) Blocked by required conditions
SDK Python / SDK Python (3.11) (push) Blocked by required conditions
SDK Python / SDK Python (3.12) (push) Blocked by required conditions
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(core): resolve Qwen 3.8 reasoning budget conflicts

* fix(core): cover unconfigured Qwen 3.8 conflicts

* chore: preserve latest main formatting

* fix(core): harden DashScope thinking precedence

* fix: honor DashScope thinking knob precedence

* test(core): assert same-layer thinking knob drop warning for request pairs (#8525)

* fix: align effort override reporting with wire resolution

* fix: resolve thinking knob review findings

* fix: sort Python SDK test imports

* fix(core): ignore null thinking knobs

* fix(core): register enable_thinking true in thinking knob selection (#8525)

selectFromLayer only registered enable_thinking === false, so a
higher-priority enable_thinking: true was invisible to cross-layer
resolution: a lower-priority samplingParams disable won selection and
rewrote the shipping tier to reasoning_effort 'none', inverting the
documented extra_body > samplingParams precedence. Register the
on-switch as the weakest knob in its own layer (an off-switch rewrites
the tier, an on-switch never does) and make the drop branch
value-aware: true keeps the shipping tier and drops only the redundant
knobs, false keeps the canonical 'none' disable.

getReasoningEffortOverride no longer reports an on-switch as shadowing
the tier (the wire drops the switch and ships the tier), except for a
request-level effort override that still shadows from under it.

Also corrects the dropConflictingThinkingKnobs contract comment (only
effort tiers ship alone; the 'none' disable and a winning budget keep
a co-present enable_thinking) and the model-providers.md precedence
callout, which overstated samplingParams precedence for older qwen
hybrids where the reasoning-derived enable_thinking: true overrides it.

* fix(core): preserve budget beneath thinking on-switch

* fix(core): canonicalize disabled thinking knobs

* fix: resolve round-6 thinking knob review findings (#8525)

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

* fix(external-context): read the response body with a reader, not for-await (#8525)

Port of #8764 (10621b3a93) to this branch. Async-iterating a
ReadableStream needs [Symbol.asyncIterator] on the TYPE, and whether it
is there depends on which lib set the program resolves — @types/node's
stream has it, the DOM lib's needs lib.dom.asynciterable. This branch
predates #8693, whose tsconfig "types" guard keeps @types/jsdom's
lib.dom out of the package program; the autofix verification build
resolves node_modules from the trusted base (which has @types/jsdom),
so the guardless branch fails the build with TS2504 on the `for await`.

The reader loop types identically in every lib set, so the build no
longer depends on that resolution. Behavior is unchanged and pinned by
the regression tests ported from the same commit: multi-chunk assembly,
the exact MAX_RESPONSE_BYTES boundary, oversize rejection with stream
cancellation, deferred-cancel sequencing, mid-stream read failure, and
invalid-UTF-8 rejection. The Mem0-related changes that share main's
http-client.ts (#8507) are intentionally not ported.

* fix(sdk-python): expose effort status reason from CLI (#8525)

The CLI emits a human-readable reason on effort_status and the
TypeScript SDK surfaces it, but the Python EffortStatus TypedDict and
_parse_effort_status dropped it, leaving Python callers to reconstruct
the reason from override. Add reason as an optional field and pass it
through, mirroring the TypeScript parser.

* test(core): add direct unit tests for selectDashScopeThinkingKnob (#8525)

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

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-12 01:49:01 +00:00
Shaojin Wen
16ff42bde7
perf(review): extend the convergence pair to 3B (chunked) reviews (#8903)
* perf(review): extend the convergence pair to 3B (chunked) reviews

The reverse-audit loop is 77-80% of the wall clock on long CI reviews
(measured on two v0.21.9 runs: 291min with 223min in the RA loop, 252min
with ~205min), and on 3B its rounds ran serially because the convergence
pair — rounds 1 and 2 launched together — was 3A-only.

The pair's arithmetic is per-territory, not whole-diff: a chunk dry in
round 1 leaves its slice of the cumulative findings list unchanged, so that
chunk's round-2 auditor re-runs substantively the same audit. Launching
`--all-chunks --round 1` and `--all-chunks --round 2` in one response runs
each chunk's two establishing audits concurrently, saving one round's wall
(~30-56min) off every chunked review — at the same one-round suppression
window the 3A pair and the pipelined loop already accept.

Orchestration-only: the CLI already builds round 2 before round 1's
transcripts exist (round 2 always fans out to every chunk; the retirement
schedule only reads history from round 3), and the deadline gate prices the
paired round-2 admission on its 600s floor exactly as the 3A pair relies on.
A new agent-prompt test pins that mechanism; SKILL.md carries the per-chunk
pair, and DESIGN.md the measurement.

* docs(review): fix the stale 3B parenthetical in the pipelined-loop bullet

The k=0 launch-coupling note still described 3B's first reverse-audit launch
as "round 1's fan-out"; with the convergence pair now applying to 3B it is
rounds 1 and 2 per chunk, matching the same fix already made at Step 4's
verifier-coupling paragraph.

* fix(review): price the 3B pair's wall at the gate and define its reporting transition

The deadline gate priced the concurrent 3B pair's round-2 build off the
seconds-old round-1 stamp — clamped to the observation floor — so both
members were committed at roughly one round's price even though their two
per-chunk fan-outs share the tool-concurrency pool and can take up to two
rounds' wall. Admissions whose predecessor is still in flight now pay both
members' wall in waves of the pool (expectedAdmissionSeconds): one round's
price when the pool holds both fan-outs at once, up to the two-round bound
when it serializes them, and the refusal degrades to round 1 alone as the
skill's budget-stop rule says.

SKILL.md's 3B pair also defined only the dry outcome; its reporting
transition now spells out waiting for both fan-outs, deduping across
rounds and chunks, one `--round 2` verifier batch riding round 3's build,
and the pair's exemption from the pipelined k/k+1 launch rule. DESIGN.md's
"packs tighter than two serial rounds ever could" claim is replaced with
the provable bound, and the gate's wave pricing is recorded beside it.

Tests pin the pair price (deadline.ts and the builder's refusal/admission
shapes) and the skill's same-response pair launch.

* fix(review): cover the both-refused pair shape and document the gate's pricing bounds

- Delegate expectedAdmissionSeconds' solo branch to expectedRoundSeconds,
  restoring one production round-cost estimator (R2-3).
- State that the pair price covers the auditor fan-outs only; the
  co-launched Step 4 verifier shards' extra wave is the reserve's to
  carry (R2-1).
- Document the pair-shaped span ledger's solo over-price as accepted
  conservatism, in the estimator doc and DESIGN.md (R2-6).
- Mirror the 3A pair annotation in the Step 5 3B copyable command block
  (R2-7).
- Make both pair refusal bullets orientation-symmetric and cover the
  both-builds-refused shape, where nothing launches and the first
  refusal's marker is the stop (R2-8).

* test(review): pin the 3B pair's reporting transition in the skill test

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-12 01:36:42 +00:00
callmeYe
a64d1291d2
feat(extensions): support Agent Plugins v1 (#8834)
* feat(extensions): support Agent Plugins v1

* fix(extensions): address Agent Plugins review blockers

* fix(extensions): address second review blockers
2026-08-11 19:45:11 +00:00
qwen-code-ci-bot
cb528e52c9
chore(release): v0.21.10 (#8942)
* chore(release): v0.21.10

* docs(changelog): sync for v0.21.10

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-11 14:30:20 +00:00
易良
7425e42fc7
fix(core,vscode): keep provider update versions in sync (#8889)
* fix(core): hash the built-in template for the stored provider version

The version recorded by a provider install hashed the installed model list
(built-ins plus every owned model), while the launch check that detects a
pending update hashed the built-in template alone. A user owning any id outside
the current built-in list — including a built-in that a release renamed away —
therefore stored a version that could never equal the recomputed one, so
"Update all" did not clear the prompt and it returned on every launch.

Record the built-in template version instead, and route both sides through a
single computeProviderTemplateVersion so the two inputs cannot drift apart
again. Custom models are still carried through the update; no new field is
persisted.

Relates to #8504

* fix(cli): scope provider version to template updates

Keep ordinary install metadata tied to the models in the install plan, while confirmed provider updates persist the detected built-in template version.

* fix: restore computeProviderTemplateVersion — align all install paths

The second commit (71bd8f1) reverted the core fix from 1df9c1d: it deleted
computeProviderTemplateVersion and restored the old resolveProviderState that
hashes the full model list (built-ins + custom). The CLI executeUpdate path
was patched post-hoc, but setup wizard, VS Code companion, and ACP flows
still went through the broken path — storing a version that would never match
the template-only launch-time check whenever custom models were present.

This commit:

1. Restores computeProviderTemplateVersion with the invariant comment.
2. Restores resolveProviderState to hash only the built-in template.
3. Removes the post-hoc version patching in executeUpdate (no longer needed).
4. Restores the comprehensive invariant tests for every built-in provider.
5. Re-exports computeProviderTemplateVersion from the core package index.

* fix(core): repair duplicate and missing test imports

The source-import pass left `resolveMetadataKey as resolveMetadataKeySrc`
imported twice and never imported `PROVIDER_METADATA_NS`, so `tsc --build`
failed on the core workspace and took Test / web-shell E2E / Post Coverage
down with it.

* fix(cli,core): use source import and shared helper in tests

- Switch provider-config test to source import (computeProviderTemplateVersionSrc)
  to match the file's convention for dist-bypass imports
- Replace hand-composed version computation in CLI test fixtures with
  computeProviderTemplateVersion to prevent drift

* fix(vscode): source plan models and version from the core preset

The IDE kept its own copy of the subscription plan model lists and hashed that
copy for `providerMetadata.<plan>.version`. Neither matched the preset the CLI
reads, so signing in from the IDE wrote a version the CLI could never reproduce
— an update prompt on the next launch — and persisted model entries that were
missing fields the preset carries. The copy had also fallen behind: it still
listed `deepseek-v4-flash`, renamed to `deepseek-v4-flash-0731` in 0.21.8, so
the IDE installed a model id that no longer exists.

Derive both the template and the version from the matching core provider,
found by baseUrl + envKey, and delete the duplicated definitions along with the
"keep in sync" comment that admitted the drift. The test no longer pins model
ids — pinning them is what let the copy drift — and asserts against the shared
preset instead.

* fix(providers): preserve installed model versions

* refactor(providers): remove redundant version helper

* fix(cli): never switch model during provider template update

* test(cli): update model selection test for unconditional modelSelection deletion

* test(cli): pin provider update metadata routing
2026-08-11 13:28:05 +00:00
Nothing Chan
73c064b44e
fix(core): exclude hook context from auto titles (#8758) (#8781)
Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local>
2026-08-11 11:03:26 +00:00
Nothing Chan
e6a3272271
feat(cli): expose reasoning effort through ACP (#8526)
* feat(cli): expose reasoning effort to ACP clients

* fix(cli): address ACP reasoning effort review

* fix(cli): address ACP reasoning effort review round 2

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

* fix(cli): migrate /effort dialog to applyReasoningEffort helper

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

* fix(acp): harden set_config_option routing and rejection messages (#8526)

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 10:32:33 +00:00
Nothing Chan
4ac606dd18
fix(core): restore deferred MCP tools on resumed sessions (#8475)
* fix(core): restore deferred MCP tools on resume (#8433)

* fix(core): reconcile resumed deferred MCP tools

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-11 10:16:17 +00:00
Shaojin Wen
3f6551f9cf
fix(review): correct the round-cap marker lifecycle and stale cap docs (#8850)
* fix(review): correct the round-cap marker lifecycle and stale cap docs

Follow-up to #8773. The round-cap stop marker shared budget-stop.json with
the time-budget stop but did not share its lifecycle rules, and the huge-diff
cap left several docs describing the old 5-round-only world.

Marker lifecycle:
- First refusal wins. Both writers now skip when a same-run marker already
  exists, so a time-budget stop followed by a retry the cap refuses no longer
  overwrites the marker and posts two contradictory stop disclosures.
- A CONVERGED exit clears any same-run marker. A round-cap refusal followed by
  a converged round (the convergence check runs before the cap gate) left a
  stale marker that capped a legitimately-converged verdict; the converged
  branch now unlinks it.
- coverage.ts's reverseByDesign exemption is now cause-aware. It suppressed
  the not-built gap for any marker, but a round-cap stop's fix (rebuild
  --round 1) is admitted — so a round-cap marker keeps the gap and its
  rebuild remediation.

Docs and tests:
- Drop the stale "retirement.ts re-exports it" and "the retirement scheduler"
  reader claims in budget.ts; note the huge-diff 3-round cap in DESIGN.md's
  LLM-call-budget paragraph and table.
- Mirror the time-budget stop's bounded-tail protocol into the ROUND CAP
  message and SKILL.md bullet (verify only via --role verify, bound the wait
  at the compose floor, no fresh re-verification pass).
- Pin the spelled cap number in the retirement note, and pin that a converged
  past-cap round exits 5 rather than refusing at the cap.

* test(review): pin the round-cap tail protocol and the per-chunk converged clear

* fix(review): recall relayed stop entries on a converged exit and align both refusal tails

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

* test(review): pin clearBudgetStop directly and the round-cap wait-bound clauses

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-11 04:36:03 +00:00
qwen-code-ci-bot
bdb7e418ba
chore(release): v0.21.9 (#8886)
* chore(release): v0.21.9

* docs(changelog): sync for v0.21.9

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-10 15:27:45 +00:00
jinye
fa8cae5418
fix(serve): Allow approved external built-in text writes (#8852)
* fix(serve): allow approved external built-in text writes

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

* fix(serve): keep write provenance off startup bundle

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-10 12:19:58 +00:00
Shaojin Wen
0d56e50b64
fix(core): deflake the shell-registry fixtures, and share the display-strip helper (#8795)
Some checks are pending
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
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* test(core): give shell-registry fixtures their own output paths

The retention-cap tests time out at 15s on shared CI runners while passing
everywhere else, and it is not load. Every settle writes a status sidecar
next to the entry's outputPath, and the fixture's default pointed all of
them at one fixed path: `/tmp/s1.output`, so `/tmp/s1.status` on every
machine that ever ran this suite. On a sticky-bit /tmp the file can already
belong to another user, and then the atomic write's rename answers EPERM
and retries with a blocking exponential backoff — 50, 100, 200ms — before
giving up. That is ~350ms per settle, measured at 362ms; the cap tests
settle 34 entries each, which is 12.3s of a 15s budget before the
assertions even run.

A/B on one machine with that exact shape (an unrenameable file planted at
the shared path): the old fixture reports `Test timed out in 15000ms`
three times over, the new one passes 57/57 in 778ms.

The fix is the fixture, not the retry policy: the backoff protects real
writes against transient EPERM and should stay. Entries now get output
paths under a per-test temp directory keyed by shellId, which the existing
afterEach already cleans, so no two entries — and no two jobs — share a
sidecar. The two tests that hand-wrote `/tmp` paths to exercise `&` and
control characters in a basename keep exactly that subject and move their
directory too.

Seen on feat/daemon-git-worktree-guard and feat/review-capture-tui, four
tests apiece, same describe block, with the file taking 120s.

* test(core): expect the notification path through production's own escape

The rebuilt <output-file> expectations computed their value by hand — a
single-occurrence String.replace, and none at all in the control-character
test — while production emits escapeXml(stripDisplayControlChars(path)),
which escapes all five XML metacharacters globally. So the expectations
were only correct while os.tmpdir() itself contained none of & < > " '.

That is the same "passes here, fails there" dependence this PR exists to
remove, in a new form and introduced by its own first commit. Probe: under
TMPDIR=/tmp/qcprobe/o&brien and .../o'brien, three tests fail with
production behaving correctly. Routing every expectation through escapeXml
makes all three environments agree — 57/57 under a plain TMPDIR and under
both poisoned ones.

Includes the pre-existing assertion in 'emits one task-notification when a
shell completes', which carried the same latent dependence and now goes
through the same pipeline.

* test(core): compose the notification path instead of re-deriving it

Third time on the same class, so this fixes the class rather than the case.
Production renders <output-file> as escapeXml(stripDisplayControlChars(p)).
My first pass hand-wrote the escape (single-occurrence, so only the first
`&`), the second added escapeXml but not the strip, and each was correct
only while os.tmpdir() happened to hold nothing the pipeline transforms — a
TMPDIR under a control character, a C1 byte, or a second \x03 failed the
assertion with production behaving correctly.

The expectations now call the real functions in the real order through one
helper, so they cannot drift from production at all. Verified 57/57 under a
clean TMPDIR, one containing \x01, and one containing `&`; dropping either
half of the composition turns three tests red under the matching hostile
TMPDIR.

(Noted while here, not changed: the registry keeps a private copy of
stripDisplayControlChars byte-identical to the exported one in
terminalSafe.ts. Out of scope for a deflake, but worth a look.)

* fix(core): use the shared stripDisplayControlChars in the shell registry

The registry kept a private stripDisplayControlChars that predates #4358:
that change added the bidi override/isolate ranges (U+202A-U+202E,
U+2066-U+2069) to the shared terminalSafe version and migrated
monitorRegistry onto it, but left this copy behind. The copies have
diverged ever since, even though terminalSafe's contract says both
background notification surfaces must apply the same defense.

The divergence bit this PR: the deflake's rebuilt <output-file>
expectations compose escapeXml(stripDisplayControlChars(...)) from the
shared utils — "exactly what production composes" was true for every
input except bidi codepoints, one more pass-here-fail-there dependence
in the class this PR exists to remove (and the earlier "byte-identical
copies" note was wrong: the shared one strips two more ranges).

Import the shared function and delete the stale copy. Notification
rendering (cwd, command label, result, output-file) now strips bidi
override/isolate characters — the documented Trojan-Source defense the
shared function exists for — and the test's composition can no longer
drift from production on any input. Net -20 lines.

* test(core): pin escapeXml's GLOBAL replace, not just its five characters

The composed oracle this PR introduced cannot discriminate a mutation in
escapeXml — it computes the expectation with the same function under test,
so both sides move together by construction. The literal case in
xml.test.ts is where that property has to live, and it carried exactly one
`&`: the other four metacharacters appeared twice and were pinned globally,
`&` was not.

Measured: mutating `.replace(/&/g, '&amp;')` to `.replace('&', '&amp;')`
ships green across the whole package — 19,546 tests passed. A real path
with two of them (a TMPDIR under `o&brien` with a basename like
`out&err.log`, the very case this PR's own history is about) would then
render a raw `&` into a model-facing XML envelope with nothing failing.

The case now carries two `&`, and the mutant turns it red.

* test(core): pin the bidi stripping the shared helper brought with it

The swap to the shared stripDisplayControlChars is NOT behaviour-preserving,
and I said it was. The shared helper carries two lines the registry's own
copy did not — U+202A-202E and U+2066-2069 — so it strips bidi overrides as
well as C0/C1, and that changes what five notification paths render. My
"byte-identical" claim came from comparing the first dozen lines and
stopping where they matched.

Measured: `/tmp/a<RLO>evil<PDI>/out.log` renders unchanged through the old
copy and as `/tmp/aevil/out.log` through the shared one. Those characters
reorder how a path DISPLAYS without changing its bytes, so leaving them in
a model-facing envelope is a spoofing surface; the stronger behaviour is
the one to keep.

Kept, then, but no longer undocumented: a registry-level test pins the bidi
stripping in the notification, and reverting to a C0/C1-only helper turns it
red. The PR title and body say what actually changes.

* fix(core): strip bidi overrides from the output tail too

The consolidation stopped one surface short, and my own test hid it: it
asserts modelText-wide absence of U+202E, which reads as whole-envelope
coverage, but its fixture shell has no output file — so <output-tail>
renders the canned unreadable form and was never exercised. The tail is the
LARGEST attacker-controllable field in the envelope, up to 8 KiB of a
background shell's own output, and it renders through a different helper
that stripped C0/C1 and passed bidi through verbatim. Probe-verified before
the fix: a shell whose output contains U+202E and U+2069 puts both into
<output-tail> unchanged.

Fixed in that helper rather than by swapping in the shared one, because the
tail must preserve newlines and carriage returns that the display helper
strips. Pinned by a test whose shell actually writes an output file — the
gap the previous pin had — asserting both that the overrides are gone and
that the line structure survives. Reverting the two ranges turns it red.

This is the Trojan-Source class (CVE-2021-42574): the characters reorder
how surrounding text reads without changing a byte, which in a model-facing
envelope means the model can be shown something other than what ran.

* refactor(core): give the bidi range set one home and pin all nine codepoints

* test(core): pin bidi stripping at the failed shell's <result> site

* fix(core): strip bidi overrides from the monitor's streaming <result> (#8795)
2026-08-10 07:57:39 +00:00
Shaojin Wen
9aec40f2d2
perf(review): cap the reverse audit and shed Agent 8 on a huge diff (#8773)
* perf(review): cap the reverse audit at one round below the sweep floor

The sweep's rationale, extended to the high tier's second pass: below
~25 effective lines a diff fits in one view, and a second reader of the
same few hunks is the same reader. The reverse audit is that second
reader run as a LOOP — to two consecutive dry audits per chunk — and on
a micro diff the loop re-reads the same lines round after round.
Measured on a 23-line one-file PR: three rounds, eleven minutes, and
the single finding round 2 produced was verifier-rejected.

The plan's budget gains reverseAuditRounds (5, or 1 below the sweep
floor; never 0 — one round IS the second look, the budget must not
scale a dimension away). Under a cap of 1 a single substantive dry
audit — or a retroactively-dry round — is the certificate, final, with
no cold check: the retirement scheduler retires on one dry audit and
reads retirement from round 2, so an all-dry round 1 exits 5 CONVERGED
at the round-2 build. A hot chunk at the cap gets a deterministic
ROUND CAP refusal from the admission gate (exit 4, the deadline gate's
termination contract, naming the unreviewedDimensions entry only when
scope is outstanding) — the cap is enforced by the builder, not by the
orchestrator counting rounds. An older or garbled plan reads as the
full cap: more auditing, never less.

* perf(review): cap the reverse audit and shed Agent 8 on a huge diff

Refocuses this PR from the micro-diff cap (which the review showed was
mis-built: micro diffs run 3A, but the cap-1 machinery lived on the 3B
path they never take, and its threshold never fired on the motivating
case) onto the huge end, which is where the six-hour timeouts actually
are.

A timeout survey found 26 review-pr jobs dying in one recent window —
~122 hours of compute, zero posted, several the same PR retried. The
wall clock is model inference (82-88% inside subagents, ~81% of that
model turns), so on a 4,000-5,300-line PR the driver is sheer volume:
dozens of agents reading the diff, then a reverse-audit loop whose every
round re-reads it against a growing findings list (~90 min a round; five
rounds alone exceed the ceiling).

The elastic budget answers this in the band where the review otherwise
posts nothing: reverseAuditRounds drops 5 -> 3 for a huge diff
(effective >= 3000; three is the smallest loop two-consecutive-dry still
converges), and specialistCap sheds Agent 8 to 0 there (a whole-diff
pass on top of the base fan-out is the marginal cost that tips a too-big
review over the wall). Neither drops a required dimension. The ROUND CAP
refusal now writes a marker compose-review caps the verdict on, so a
non-converged stop discloses like a budget stop rather than resting on
the orchestrator's relay.

Removing the cap-1 tier resolves the review's two Criticals and
collapses the retirement scheduler back to its clean two-dry logic; the
duplicated plan-cap read is gone (agent-prompt reads the parsed report,
retirement no longer needs the cap at all). Tests pin the huge/normal
boundary, the effective-vs-source split, round-past-cap enforcement at
both 3 and 5, and the marker round-trip.

* refactor(review): tidy round-cap prose left from the earlier iteration

- Drop the always-plural ternary in the retirement note: dryRounds is a
  two-tuple again, so the singular branch is dead prose.
- SKILL.md's merge bullet: the loop's bound is the plan's round cap (5, or
  3 for a huge diff), not a flat 5-round cap.
- Re-wrap the overlong deadline.ts header line.

* fix(review): address reverse-audit round-cap review feedback

- Floor `reverseAuditRoundCap` at the huge-diff cap of 3: an out-of-band
  1 or 2 reads as the full cap, never fewer rounds.
- Delete the dead `REVERSE_AUDIT_MAX_ROUNDS` re-export (no consumers) and
  point the retirement cold-check comment at the plan cap.
- Correct the cap-3 rationale everywhere it was mechanically wrong: three
  is one audit round above the convergence floor of two (the all-dry
  rounds-1-and-2 shape converges under any cap of two or more, since the
  convergence check runs before the cap gate), not the smallest converging
  loop — in budget.ts, budget.test.ts, DESIGN.md and SKILL.md.
- Define `effective` in the SKILL.md budget bullet and make the round-5
  narration cap-agnostic; scope the retroactive-dry example to the cap-5
  shape.
- Add tests for the cap-3 retirement certificate, the per-chunk and
  chunkless round-cap gates, the undefined-round marker, and the
  specialistCap effective-vs-src dependence.

---------

Co-authored-by: verify <verify@local>
2026-08-10 07:28:36 +00:00
Heyang Wang
e46586782c
feat: support drag and drop img in web-shell (#8696)
* feat(web-shell): support image drag and drop

Allow Web Shell composers to ingest image files reliably while
preserving the existing multimodal prompt protocol.

- Share ordered image ingestion across desktop and mobile editors
- Support image-only prompts and BMP preview and provider-safe handling
- Preserve queued payloads across retries and uncertain outcomes
- Add lifecycle guards, user feedback, unit coverage, and browser tests

* fix(web-shell): harden image prompt admission recovery

Preserve complete prompt payloads and prevent duplicate or uncertain
delivery states when admission responses race with queue lifecycle
events.

- Correlate admission, queue, and terminal events by prompt ID
- Restore images and input annotations across retry and edit flows
- Bound image reader concurrency and encoded attachment memory
- Reconcile confirmed removals and explain ambiguous queue entries

* docs(web-shell): align image drag design with review fixes

Document the reviewed admission, recovery, and resource invariants.
Keep the design aligned with the hardened Web Shell implementation.

- Record bounded image ingestion and encoded-data budgeting
- Clarify prompt lifecycle correlation and confirmed removal behavior
- Describe annotation restoration and internal action boundaries
- Update focused validation evidence and acceptance criteria

* fix(web-shell): avoid duplicate restored attachments

Skip payload attachments when restoring text is a no-op because the
same prompt text already exists in the composer.

- Restore images and annotations only when their text is inserted
- Preserve image-only restoration regardless of the current draft
- Add regression coverage for duplicate text with attachments

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-08-10 05:48:17 +00:00
Shaojin Wen
af372e5a21
perf(review): guarantee compose survives a reverse-audit budget stop (#8791)
* perf(review): guarantee compose survives a reverse-audit budget stop

PR #8687 — a 4,269-line cross-worktree git guard — timed out after six
hours and posted nothing, holding ~20 E2E-confirmed Critical bypasses.
The deadline gate worked: it refused round 3 correctly with ~110
minutes and the whole reserve in hand. The tail after the stop was the
killer — a single hand-rolled verification agent re-running a 15-family
shell/git bypass battery with real filesystem E2E consumed all of it,
and the wall hit mid-verification before compose-review ever ran.

The reserve was one number covering "verification + compose + submit",
which is right for a normal per-finding re-trace but wrong for a
security PR where verification cost is unbounded (real E2E per finding)
while compose and submit stay bounded. So a distinct, smaller compose
FLOOR is carved out and the VERIFIER — not the reverse-audit builder —
is gated on it: below the floor `agent-prompt --role verify` refuses to
build (VERIFY BUDGET, exit 4), the findings keep their `— [unverified]`
tag for compose-review to cap, and compose runs. The floor is strictly
below the reserve, so a healthy run reaches the reverse-audit gate first
and never sees it; it is the cover for the one span the reserve cannot
bound.

The prose closes the bypass the gate cannot see: the post-stop tail
verifies only through the gated builder, never a hand-rolled agent, and
invents no fresh re-verification pass for findings already confirmed —
compose and submit are non-negotiable. DESIGN.md records the incident;
the RA budget message and SKILL Step 5 tail are rewritten to match.

* fix(review): close the round-1 gaps in the compose-floor gate

- R2-2 (Critical): the documented `0` escape hatch did not disable the
  verify gate past the deadline — `remainingSeconds` goes negative there
  and `negative >= 0` is false, firing the supposedly-disabled gate.
  verifyBudgetExhausted now returns null the moment the effective floor is
  0, before the comparison. Pinned with a past-deadline case.
- R2-1 (Critical): the gate bounds prompt CONSTRUCTION, not the wall time
  of an already-admitted verifier that then runs a long E2E past the
  floor — and agent-prompt builds prompts, it cannot cancel a running
  agent. The SKILL tail now tells the orchestrator to bound the WAIT: when
  the deadline is within the compose floor and a verifier batch has not
  returned, stop waiting on it, keep its findings unverified, and compose.
  The remaining execution-time cancellation is a harness capability, noted
  as such (same layer boundary as the hand-rolled-agent caveat).
- R2-3: the agent-prompt exit-code help now documents both the BUDGET and
  VERIFY BUDGET exit-4 refusals.
- R2-4: the reverseAuditBudgetMessage test now pins the new tail rules
  (gated verifier only, no hand-rolled agent, no re-verification).
- R2-5: docs/users/features/code-review.md documents the compose floor —
  default, env var, reserve nesting, exit-4 behaviour, zero hatch.

* fix(review): round-2 fixes for the compose-floor gate

- R3-1 (Critical): the verify gate admitted at exactly the floor, where
  the first work crosses below it — the floor is compose-only with no
  margin, so it now refuses at equality (`> floor`, unlike the RA reserve
  which admits at exact cover). Exact-boundary test flipped.
- R3-2 (Critical): the refusal message and SKILL claimed unverified
  findings "post as needing human review", but the confirmed-only rule
  keeps tagged details terminal-only. Reworded to the true contract:
  compose-review caps the verdict and discloses the verification gap; the
  tagged details stay terminal-only; what posts is the earlier rounds'
  confirmed findings plus that gap.
- R3-5: extracted readDeadlineSeconds / readNonNegativeSeconds, shared by
  both gates so the fail-open contract lives in one place.
- R3-3: pinned the verify gate's fail-open branches (malformed/non-positive
  deadline, past-deadline negative remaining, negative-floor fallback).
- R3-4: pinned the floor-minutes rendering (a field swap to remainingSeconds
  would misstate the protected floor).
- R3-7: pinned that a refused verifier writes no budget-stop marker and no
  admission stamp.
- R3-8: pinned validation-before-gate (a malformed verify call under the
  floor throws, not exit 4).

R3-6 needs no change: the SKILL.test pointer<->heading gate already covers
the DESIGN section (a dangling pointer fails it).

* fix(review): round-3 cheap fixes for the compose-floor gate

Low-risk corrections; the two edge-case Criticals (R4-1 broken-plan
masking, shared with the RA gate; R4-2 compose-review relaunch FIX) are
left as follow-ups — noted on the threads.

- R4-4: the readDeadlineSeconds extraction stranded reverseAuditBudgetExhausted's
  contract JSDoc above the helper; moved it back onto the function.
- R4-5: the round-2 "terminal-only, never posted" wording contradicted
  compose-review's own verdict line ("posted, disclosed as unverified") —
  a pre-existing contract ambiguity this PR should not relitigate. Reworded
  the message and SKILL to the invariant both readings share: an unverified
  finding is never treated as a confirmed blocker; the verdict is capped.
- R4-7: "below the N-minute floor" contradicted the exact-equality refusal
  (the gate admits on `> floor`); now "at or below", in the message and the
  user docs.
- R4-3: pinned that a blank/whitespace floor override falls back to the
  default (only explicit 0 disables).
- R4-6: pinned the negative-remaining clamp in verifyBudgetMessage.

---------

Co-authored-by: verify <verify@local>
2026-08-10 02:50:45 +00:00
Shaojin Wen
33e602cc41
fix(test): stop background-shell tests sharing a fixed /tmp sidecar path (#8813)
* fix(test): stop background-shell tests sharing a fixed /tmp sidecar path

`makeEntry` defaulted to `outputPath: '/tmp/s1.output'`, so every entry in
this file — across tests, across workers, across CI jobs on the same host —
mirrored its status sidecar to the single path `/tmp/s1.status`.

`/tmp` carries the sticky bit. Once that file belongs to another uid, the
atomic rename in `atomicWriteFileSync` fails EPERM, and
`renameWithRetrySync` burns its full 50+100+200ms backoff before the
registry swallows the error. Every register/complete then costs ~350ms and
the sidecar never lands.

That is what the loop tests were paying: the retention-cap cases do 68
register/complete calls, and CI measured 23.8s each. The durations across
the whole file were exact multiples of 351ms — 352 / 703 / 1405 / 2113 /
3520 — with no variance, which is the backoff sum, not disk latency.

Give each entry its own temp directory instead. The shared path is gone,
the rename succeeds, and the file drops from 128.9s to 3.2s locally with
`/tmp/s1.status` made immutable to reproduce the CI condition.

#8797 raised these four cases to a 120s timeout to survive the cost. With
the cost removed the band-aid goes too, so a future regression fails loudly
instead of silently taking two minutes.

* fix(test): unify sidecar test helpers and pin per-entry outputPath uniqueness

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

* test: assert the whole output-file element, not just its tail

Unifying the sidecar helpers moved these two entries onto random temp
directories, and the assertions were relaxed to a suffix match to cope.
Rebuild the expected element from the path under test instead: the temp
prefix is random, but the escaping and control-byte stripping these two
cases exist to pin are exact.

* test: escape expected XML paths the way the registry does

The anchored `<output-file>` assertions built their expected value by hand —
one replaced `&` only, the other nothing at all. `tmpdir()` may legally
contain XML metacharacters (`&` on Windows, `<` on POSIX), so those cases
became environment-dependent the moment they moved off the fixed `/tmp`
path.

Run the expected path through the same `escapeXml(stripDisplayControlChars())`
the registry uses. Verified with `TMPDIR=/tmp/qwen-xml-probe/a&b<c`: the
helper passes all three focused cases, while the hand-rolled version fails
two.

* test: escape last dynamic output-file expectation, stop sidecar leak

The escape conversion in 5549db2348 covered two of the three dynamic
<output-file> expectations; `emits one task-notification when a shell
completes` still interpolated the temp path raw, so a TMPDIR containing
XML metacharacters (legal on POSIX and Windows) failed it. Route it
through the same expectedOutputFileElement helper.

Also remove the `<dir>.status` sidecar the directory-outputPath test
leaks next to its temp dir on every run; afterEach only tracks the dir.

* test(core): clean the dir-outputPath sidecar in afterEach, not after assertions (#8813)

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

* fix(core): reuse shared display sanitizer in background shell registry (#8813)

The registry kept a private stripDisplayControlChars that only removed
C0/C1 controls, while the shared terminalSafe helper also strips Unicode
bidi overrides and isolates. With a TMPDIR containing one of those
characters the test expectations (built with the shared helper) lost a
character the notification XML retained. Reuse the shared helper so both
background notification surfaces apply the same Trojan-Source defense and
the expectations cannot drift from the implementation again; extend the
control-character test to pin the bidi stripping.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-10 01:53:58 +00:00
qqqys
55e20db328
feat(workflows): add an orchestration policy layer to the Workflow tool description (#8694)
* feat(workflows): add an orchestration policy layer to the Workflow tool description

The description was an accurate technical specification that said nothing
about when to orchestrate, which shape to use, or how to trust what comes
back. With only the API in view, the naive shape wins every time: fan
everything out through one barrier and take the first answer at face value.

Adds the decision layer on top of the existing runtime facts — purpose
framing, pipeline-by-default with an explicit test for when a barrier is
genuinely required, scout-then-orchestrate, reusable shapes, adversarial
and perspective-diverse verification, the deduplicate-against-seen rule
that keeps discovery loops terminating, and honest reporting of bounded
coverage.

Prompt text only: no runtime, sandbox, or schema change.

Closes #8690

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

* docs(workflows): close the description's limit gaps and pin every policy section

Addresses the round-1 review on #8694 (R1-1 through R1-6).

The description now names the three limits a model has to plan around
rather than discover from a mid-run failure: `workflow()` nests one level
only, the 30-minute wall-clock cap per run
(`QWEN_CODE_MAX_WORKFLOW_SECONDS`), and the per-run output-token cap —
surfaced as a `budget.total` probe rather than a knob name, keeping the
P5 R2 rule that the env var stays out of model-reachable text.

The pinning test grew anchors for the three policy sections a mutation
probe could delete while it stayed green, and for
`QWEN_CODE_MAX_WORKFLOW_CONCURRENCY`, which the description advertised
with nothing asserting its spelling.

The JSDoc claim that the authoring contract "is not duplicated here" was
false — the limits and env knobs appear in both halves — so it now says
they are a summary to keep in sync.

* docs(workflows): anchor the planning caps and pair each env knob with its limit

Round-2 review (R2-1/R2-2/R2-3) plus the two non-blocking suggestions from
the maintainer verification round.

- R2-1: anchor the two env knobs through the orchestrator's exported
  MAX_WORKFLOW_AGENTS_ENV / MAX_WORKFLOW_CONCURRENCY_ENV instead of
  hardcoded literals, so a rename on the runtime side fails the guard too.
  QWEN_CODE_MAX_WORKFLOW_SECONDS has no exported constant
  (workflow-sandbox.ts reads it inline), so it stays a literal.
- R2-2: pin the numbers the model plans around — "up to 1000 agents total"
  and the "30-minute wall-clock cap" — not just the knob names.
- R2-3: the sync note claimed the whole limits block restates the `script`
  contract; the wall-clock cap, token budget and one-level workflow()
  nesting limit live only in the tool description. Say which is which.
- W-1: budget exhaustion refuses each further agent() call; a bare
  sequential await sees the rejection, while parallel()/pipeline() turn the
  refused slot into null and keep running (errors-as-data, settleToNullArray).
- W-2: pair each env knob with the quantity it overrides — the shared
  parenthetical read as if both knobs covered the total.

Mutation-probed: 1000->500, 30-minute->15-minute, and renaming
MAX_WORKFLOW_AGENTS_ENV's value each turn the suite red on the intended
assertion; restored tree is 40/40 green.

* fix(workflows): derive description caps from the runtime constants

Addresses the four Suggestions from review round 3 on #8694.

R3-2 / R3-3: the agent cap and the two env-knob names were prose
literals in both model-visible halves — the tool description and the
`script` parameter description — a third copy sitting in the pinning
test. Interpolate `DEFAULT_MAX_AGENTS_PER_RUN`, `MAX_WORKFLOW_AGENTS_ENV`
and `MAX_WORKFLOW_CONCURRENCY_ENV` from `workflow-orchestrator.ts` into
both halves instead, so raising a cap moves every copy at once and the
model can no longer read two contradictory caps from one tool call.
`DEFAULT_MAX_WALL_CLOCK_MS` stays a literal: it is private to
`workflow-sandbox.ts`. The doc comment now says which values still need
hand-syncing rather than claiming all of them do.

R3-1: the numeric anchor pinned the description's own literal, so it
could not catch the drift its comment claimed to catch. Anchor it
through the exported constant, and add a test pinning the `script`
parameter description's copy of the same caps — nothing asserted it
before, so a maintainer could raise a cap, get the tool-description
test green again, and leave `script` advertising the old number.

R3-4: issue #8690 asked the text to use this project's vocabulary. The
description advertised `workflow('<name>')` without ever saying where
saved workflows live, leaving the model to guess a name blindly or
construct an absolute `scriptPath` it has no way to know. Name both
scopes and their precedence.

Verification: npm run build, npm run typecheck, eslint on both changed
files, and vitest on workflow.test.ts + workflow-orchestrator.test.ts
(174 passed). Probed the new anchors both ways with the cap temporarily
raised to 2000: the suite stays green (the description tracks the
constant), and pasting the literal `1000` back into the description
turns both assertions red.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 17:50:21 +00:00
jinye
0a3d7bb5c1
feat(acp): Protect against repeated tool execution failures (#8469)
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 / 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
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
* feat(acp): protect repeated tool execution failures

Add a conservative prompt-local guard for repeated typed ACP tool execution failures, with shadow/warn/enforce rollout modes, privacy-safe telemetry, and coverage for the final execution outcome contract.

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

* fix(acp): harden repeated tool failure guard rollout

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

* fix(acp): address repeated failure guard review

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

* fix(acp): improve repeated failure guard recall

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

* docs(acp): clarify review and rollout gates

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-09 15:47:38 +00:00
jinye
a810f7e16c
fix(serve): Make session restore timeouts safe and observable (#8691)
* fix(serve): make session restore timeouts safe

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

* fix(cli): restore missing core mock exports in the ACP worktree suite

The restore-tracing change added `extractDaemonTraceContext` and
`withDaemonSpan` to `acpAgent.ts`, but `acpAgent.worktree.test.ts`
replaces `@qwen-code/qwen-code-core` with a full mock factory that never
listed them. `loadSession` then failed on an undefined export, taking all
three cases down and producing teardown rejections from the half-built
agent. The sibling suite was updated; this one was missed.

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

* fix(serve): bound and disambiguate the abandoned restore lifecycle

Four follow-ups from review of the restore timeout work.

A startup budget may now raise the restore budget but never lower it.
Taking an explicitly configured `initializeTimeoutMs` as the restore
fallback meant a deployment that tightened its child-initialize check
still inherited a sub-default restore deadline — exactly the failure this
change exists to remove. An explicit `sessionRestoreTimeoutMs` still wins
outright, including below the default, for deployments that want restore
to fail fast. Validation now names the field actually at fault.

A restore fenced behind a timed-out predecessor is no longer reported as
an ordinary in-flight restore. It carries `reason:
awaiting_abandoned_cleanup` and a retry hint of one restore budget
(capped at 120s) instead of the ordinary 5 seconds, because the fence
cannot clear until the non-cancellable ACP request settles and a 5-second
cadence just spins the caller against a 409 it cannot resolve.

Whether a channel is condemned is now derived rather than sticky. A
timeout recorded `emptyReapPending` permanently, so any channel that had
ever seen one was guaranteed to be reaped once its remaining work
drained, forcing a cold respawn even when the late restore had landed and
closed cleanly. The reap condition is now computed from an outstanding
`unsettledAbandonedRestores` set, quarantine, or an ordinary pending
empty reap; real settlement clears the entry and hands the channel back
to the configured idle policy.

Abandonment no longer retains ownership without bound. One further
restore budget after the deadline, a still-unsettled restore marks the
channel `restoreSettlementOverdue`: existing sessions and workspace
control keep working, but fresh session work is refused so the channel
can drain, since closing the transport is the only lever that releases a
permanently hung request. Releasing capacity while hidden work runs would
allow unbounded oversubscription, and force-killing a channel with live
siblings would reintroduce the failure this work removes, so neither is
done. Fresh-admission blocking is now scanned across alive channels
rather than tracked in a single reference, so a second condemned channel
cannot silently displace the first.

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

* fix(serve): keep the abandoned restore lifecycle off ids it no longer owns

Two correctness gaps in the abandoned-restore machinery introduced by this
PR, both reported by automated review and both confirmed by mutation
testing (each new test fails when its fix is reverted).

A caller-supplied `sessionId` is used verbatim by the agent, but
`spawnOrAttach` never consulted `inFlightRestores`. A fresh spawn could
therefore take an id that a restore still owns, in either lifecycle phase.
The consequences were silent: `abandonedRestoreIds` suppresses session
updates, guardrail events, and child notifications, so the new session
would have registered successfully and then emitted nothing; and a late
`settleAbandonedRestore` would have closed and tombstoned it out from
under its owner. Such a spawn is now rejected with the same
`RestoreInProgressError` and reason the restore path uses, so the caller
gets the correct retry hint for whichever phase is holding the id.

The cleanup path is guarded independently, because the request-level check
only covers the id the caller asked for and a session registers under the
id the child returns. An abandoned restore never reaches
`createSessionEntry` — the deadline rejects before registration — so any
live entry under that id belongs to someone else. Cleanup now detects that
and returns without closing or tombstoning, releasing its own bookkeeping
instead.

The notification fence has no TTL and was only cleared by
`markRestoreInFlight`, which covers a subsequent restore and nothing else.
`createSessionEntry` now clears it for every registration route, so a
legitimate owner of the id is never handed a session that silently drops
everything the child sends it.

Also tightens two tests that could not observe the values they pin. The
SDK default restore timeout admitted any value in (30s, 70s]; it is now
split at the exact boundary, so collapsing the default onto the 60s server
budget — which would make the client abort race the daemon's own deadline
and cost the caller its structured 504 — fails. And the advertised-budget
propagation from capabilities through to the SDK call had no live-path
assertion; dropping the capabilities argument at the real call site left
every existing test green. The `as never` casts are replaced with typed
`DaemonCapabilities` values so a field rename fails typecheck.

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

* fix(serve): let a condemned channel drain without its wedged child

Merging main's active-work close protocol (#8588) into this PR's abandoned
restore bound produced a deadlock that neither side has on its own, and the
conflict resolution was committed without running tests.

`maybeCloseIdleSession` now routes through `confirmChildUnheld`, which asks
the child whether it still holds work before closing a session nobody is
attached to. That is right in general and wrong for a channel this PR has
already condemned. `restoreSettlementOverdue` and quarantine exist precisely
because the child stopped being answerable, and their whole premise is that
visible work drains so the channel can be reaped — closing the transport is
the only thing that can release a restore we cannot cancel. Making that
drain depend on a round trip to the wedged child inverts it: a child stuck
in a non-cancellable restore is exactly the one that cannot reply inside
`ACTIVE_WORK_CLOSE_TIMEOUT_MS`, so the sessions never close, the channel
never drains, the reap never fires, and the bound never takes effect.

A channel condemned by the restore lifecycle now skips the round trip and
proceeds to local teardown. Nothing is attached to the session by then —
`maybeCloseIdleSession` gates on that — and the sibling-safety invariant is
untouched: this closes sessions whose clients have already left, it does not
force-kill a channel that still has live ones.

The regression test drives an overdue channel whose child never answers the
close-if-unheld probe and asserts the detach still reaps it. Reverting the
guard reproduces the deadlock as a test timeout.

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

* test(serve): pin the restore-timeout contract the review found unasserted

Automated review identified eleven places where the restore-timeout work's
behavior was correct but unpinned — each with a mutation that ships green.
Every fix below was verified the same way: apply the mutation, watch the new
assertion fail, revert, watch it pass.

The timeout path's telemetry had no coverage at all, which is the sharpest
gap given that observability is what this work exists to deliver. A shared
recorder now asserts the public timeout result and its kill_empty-vs-
fence_shared signal, the late arrival, and the cleanup outcome for both the
closed and quarantined cases.

The deadline timer's cancellation on a successful restore was likewise
unpinned: deleting both `clearTimeout` calls kept the whole suite green,
while in production the stale timer fires one budget after a successful
restore and abandons a live session — fencing its frames, closing its event
bus, and emitting a spurious timeout. A success-path test now advances past
the deadline and asserts no second public result.

Three more bridge assertions proved less than they claimed: the concurrent-
restore case never checked that the abandoned restore settles, the
workspace-control case never checked that the deferred reap eventually
fires, and the resolver never pinned the accepting side of the MAX boundary
(a `>` to `>=` mutation rejects the largest legal delay at boot). The
workspace-control case also needed a positive channel idle budget, since
with the default zero the idle-timer kill substitutes for the reap junction
under test; its assertions are rewritten around the derived reap semantics
rather than the sticky flag they predate.

Outside the bridge: the scheduled-task timeout wiring had no test, so
deleting the arguments silently fell back to the helpers' own defaults; the
cold restore path never asserted that `live_restore_ms` is absent; the SDK's
per-request validation and its over-ceiling clamp were untested; the WebUI
watchdog test jumped straight to its own value, staying green for any
watchdog at or below it, including the 30s attach value that would recreate
the original symptom in the browser; and the two new known error types were
unexercised, so dropping either would relabel every restore-timeout and
quarantine error as unknown.

Two review items are deliberately not taken here and are recorded in the
design doc's non-goals instead: transcript materialization is still not
separately attributable from `config_setup`, which needs instrumentation
inside the core session loader that P1/P2 restructures anyway, and sibling
event-loop latency during a large restore remains unmeasured.

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

* fix(serve): bound the condemned-channel close and complete the fence contract

Second automated review round, on the code the first round produced. One
Critical and twelve suggestions; all verified by mutation before and after.

**The Critical is a regression I introduced.** Letting a condemned channel
skip the bounded hold probe routed it into `closeSessionImpl`, whose agent
close is unbounded when it throws on failure — so the fix traded a bounded
wait on a wedged child for an unbounded one. A settlement-overdue channel
with an unresponsive child would hang `detachClient` forever, strand the
session in `closing`, never drain, never reap, and 503 every new session
until restart: strictly worse than before. `CloseSessionOpts` now carries an
`agentCloseTimeoutMs` that the condemned path sets, so a hang lands in the
existing unknown-outcome recovery, which kills the channel — the teardown
the drain was waiting for. The earlier test missed this because its fake
child still answered the plain close; it now answers nothing at all, and
asserts the detach itself returns.

**The fence was invisible on the transports clients actually use.**
`toRpcError` had no `RestoreInProgressError` case, so over acp-http and
acp-ws — which SDK negotiation prefers over REST — the fence degraded to an
opaque internal 500 with no code, reason, or hint, and the backoff contract
this work documents was impossible to honor.

**Two retry hints still advertised five seconds for states that outlive a
budget.** The restore 504 creates the fence, and quarantine lasts until the
channel drains; a fresh-id caller never reaches the 409 that carries the
real hint, so its header was the only signal it got. Both now derive from
the budget through one shared clamp helper, which also replaces the formula
that was inlined in the bridge and gives the documented 5-120s bounds a
test.

**A spawn collision reported an operation the caller never issued**, naming
the restore owner's action as both the active and the requested one and
telling the caller to retry an endpoint it never called.

The rest: five places still described the initialize-timeout fallback as a
plain chain rather than raise-only, contradicting sibling docs shipped in
this same PR; the design doc omitted the retry-hint clamp; the protocol
reference omitted the new spawn emission site; the error taxonomy omitted
`restore_settlement_overdue`, which matters because its audience is
monitoring. Test-only gaps: the dynamic 409 had no HTTP-layer coverage, the
120-second cap was unpinned, and the SDK's precedence of an explicit global
timeout over the advertised budget was pinned only branch-by-branch.

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

* fix(serve): preserve restore session ownership handoff

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-09 15:47:09 +00:00
Shaojin Wen
3e731cda8b
fix(test): deflake three CI-load-sensitive tests (#8797)
* fix(core): deflake auto-memory extract tests under CI load

waitForMockCall polled ten zero-delay event-loop turns and gave up. The
mock it waits for fires after real async work (index rebuilds, cursor
I/O), so on a loaded CI runner the poll spun through its ten turns
without waiting any wall-clock time and the two rebuild-isolation tests
failed with 'Expected mock to be called' (seen on PR #8773's Test
(ubuntu-latest) job). Wait against a 2s deadline instead; the fast path
still returns on the first check.

* fix(cli): give the manifest-context fixture teardown a real timeout

The afterAll deletes several 16k-entry fixture trees — tens of thousands
of unlinks — and blew past vitest's default 10s hook timeout on a loaded
CI runner (PR #8773's second Test (ubuntu-latest) run), failing a suite
whose 59 tests had all passed. Same CI-load flake class as the extract
test fix in this branch.

* fix(core): let the shell-registry retention tests pay for their sidecar I/O

Each register/complete in the retention-cap loop tests also writes the
status sidecar via atomicWriteFileSync, and loaded CI runners have been
measured at ~700ms per sidecar write — the ~70 writes of the longest
loop take ~50s, past vitest's 15s default, failing four tests whose
assertions are pure eviction semantics (seen twice on PR #8773, on two
different runners). Give them an explicit 120s timeout; no assertion
changes.
2026-08-09 08:58:50 +00:00
jinye
60458f5e37
fix(serve): Coordinate caller-supplied session IDs (#8415)
* fix(serve): coordinate caller-supplied session IDs

Complete daemon-wide admission across REST, ACP, workspace generations, SDKs, and MCP.

Closes #8411

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

* test(serve): wire session bridges in hot-reload harness

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

* fix(serve): address review round for caller-supplied session IDs (#8415)

Restore the observability and fail-loud guarantees flagged in review:
log every session-id admission routing failure, name the live foreign
owner workspace in restore conflicts, make the ACP dispatcher's
admission dependency required so load/resume cannot run on a mount
without one, and require mountAcpHttp hosts to inject the daemon-wide
admission instead of silently building a weak fallback. Harden the SDK
WS transport against environments without global fetch and against
non-capabilities 200 envelopes, and align the design doc with the
implemented restore-sharing and persistence-failure semantics.

* fix(sdk): harden session ID capability fallback

Preserve REST capability errors, fail closed on malformed envelopes, retain restore routing diagnostics, and align retry documentation.

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

* fix(serve): normalize restored session IDs

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

* fix(session): preserve mixed-case legacy session access

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-09 07:31:30 +00:00
Shaojin Wen
4a79517815
feat(cli): surface the posted review link from /review submit (#8770)
The Create Review response's html_url was parsed for the receipt id and
then dropped, so the terminal summary had no deterministic link to the
review just posted — in the Web Shell there is no scrollback to recover
it from. submit now relays html_url on the Posted stderr line and as
`url` in the stdout JSON (best-effort, like the receipt), and SKILL.md
requires the final summary to carry a `Posted: <url>` line before the
fixed `Review complete:` line.
2026-08-09 01:28:56 +00:00
qwen-code-dev-bot
ad7f905d38
fix(cli): improve WebSearch no-model notice with a copy-paste config example (#8665)
* fix(cli): improve WebSearch no-model notice with a copy-paste config example

The startup notice when webSearch is enabled but no model is set only
said "Set tools.webSearch.model to a model declared under
modelProviders" without showing what that looks like. Users had to
guess the modelProviders shape.

Expand the notice to include a minimal working settings.json example
(tools.webSearch + a DashScope modelProviders entry) and the env-var
alternative, so users can copy-paste to get web_search working.

* fix(core): complete WebSearch env recipe in no-model notice (#8665)

* fix(core): single-source WebSearch notice endpoint and pin recipe (#8665)

* test(core): pin notice example baseUrl in WebSearch gate test (#8665)

* fix(core): state WEB_SEARCH_BASE_URL value in WebSearch env recipe (#8665)

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

* fix(core): split WebSearch env recipe line in no-model notice (#8665)

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

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 23:27:01 +00:00
Nothing Chan
1774771317
fix(telemetry): ignore unsupported OTel exporter selectors (#8703)
* fix(telemetry): ignore unsupported OTel exporter selectors (#8697)

* test(telemetry): guard exporter env scrub and its finally restore (#8697)

The env-scrub assertions ran inside the mocked start(), where
initializeTelemetry's init-failure catch swallowed them — the test
passed even with the fix reverted. Record observations during start()
and assert after init resolves, add a throw-path test proving the
finally block restores the caller's environment, and note in sdk-impl
that spanProcessors/logRecordProcessors must stay unconditional arrays
because the sdk-node logs env fallback runs in the NodeSDK constructor,
outside the scrub window around start().

---------

Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local>
2026-08-08 23:12:43 +00:00
易良
1a5d1c445e
fix(core): confirm read-only git commands when repo config executes programs (#8575) (#8645)
* fix(core): confirm read-only git commands when repo config executes programs (#8575)

Whitelisted read-only git sub-commands (status, diff, log, show, ...) are
auto-approved based purely on command text, but git can execute programs
configured in the repository-local config while running them: diff.external,
core.fsmonitor, core.pager / pager overrides, diff driver textconv,
core.askpass, credential.helper, core.sshCommand, remote proxies,
ext:: remote URLs, gpg.program. A planted .git/config could turn an
auto-approved command into arbitrary code execution.

Add a synchronous repo-local config probe (bounded stat walk + small file
reads, fail-closed) shared by the AST and regex classifiers: when a git
command would classify as read-only and the repo-local config reachable
from the execution cwd contains program-executing keys, the verdict is
downgraded so the command requires confirmation. Global/system config is
deliberately out of scope (the user's own setup, not a cloned-repo
attack surface). All permission entry points (shell tool, monitor tool,
permission manager, memory-scoped agent policy) now pass the execution
cwd to the classifier. Classifier APIs only gain an optional parameter;
behavior without cwd is unchanged.

* fix(core): close two probe gaps from review of #8575

- Speculation gate now receives the execution cwd: speculated shell
  calls bypass the permission flow, so evaluateToolCall passes cwd
  (and the shell directory arg, which takes precedence) into
  classifyShellCommandSafety. A speculated `git diff` in a repo with
  diff.external planted now hits the boundary instead of executing.
- Probe reads `.git/config.worktree` of the main checkout too — with
  extensions.worktreeConfig enabled git reads it for the main
  worktree, so a key planted there no longer bypasses the probe.
- plan-mode shell policy passes its effective cwd to the classifier
  for consistent classification (no execution hole there; consistency).
- Document bare repos as out of scope.
- Add end-to-end integration test driving the real probe + classifier
  through ShellToolInvocation.getDefaultPermission (no fs mocking).

* fix(core): honor Git worktree config semantics

* fix(core): fail closed on opaque git config constructs (#8575)

Round-3 hardening of the config probe, closing bypasses found in local
security review (all empirically reachable via attacker-written
.git/config):

- Section headers the minimal parser cannot interpret (e.g. `]` inside a
  quoted subsection) now fail closed instead of silently dropping the
  entries beneath them.
- Inline `[section] key = value` lines are parsed instead of discarded.
- Unparseable `.git` pointer files fail closed like unreadable ones.
- include/includeIf entries are flagged rather than resolved: their
  targets can live outside `.git` (e.g. tracked working-tree files).
- core.gitProxy added to the program-valued keys (git:// transport via
  whitelisted `git remote show`).
- Document the cd-into-another-repo limitation in the module doc.
- Add the missing PermissionManager cwd-threading contract test
  (dirty repo config → ask, clean → allow) and regression tests for
  each behavior above.

* fix(core): track cd in git config probe; close filter/url bypasses (#8575)

Round-4 hardening from the local security/correctness review — each item
was empirically demonstrated against the prior head:

- Compound commands now track cd/pushd/popd: statically resolvable
  targets move the probe's base directory (same-repo `cd subdir` stays
  read-only), unresolvable targets (`cd`, `cd -`, `cd $VAR`, `popd`,
  quoted/expanded targets) downgrade later git segments. Closes the
  `cd <dirty-repo> && git status` bypass in both the AST and regex
  classifiers, including tree-sitter's nested-list chains.
- filter.<name>.clean/smudge/process flagged: `git diff` runs worktree
  content through the configured clean filter with no extra flags.
- url.<base>.insteadOf rewrite targets starting with ext:: flagged
  (combined with protocol.ext.allow in the same file this executes on
  whitelisted `git remote show`).
- Config reads are size-capped at 1 MiB and fail closed above it (DoS
  guard for the synchronous permission path).
- Boolean pager overrides (pager.<cmd> = true/false) no longer flagged.
- Added the missing wiring contract tests: PermissionManager
  config.getCwd() fallback, memory-scoped agent shell policy, plan-mode
  shell policy (including the directory-param override).

* fix(core): provide getTargetDir in speculation test mocks (#8575)

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

* fix(core): harden git-config exec probe against cd-tracking bypasses (#8575)

Address review round 1 findings on the repository-local git config
execution probe:

- Track cd/pushd across every sibling-statement sequence (program, brace
  group, subshell body), not only `&&` lists; propagate the directory out
  of brace groups and redirected/negated wrappers.
- Respect list-operator semantics: cd state no longer leaks across `||`
  or `&`, and non-`&&` sequential statements keep the prior directory in
  the safety equation.
- Resolve cd targets strictly: skip flag arguments (`-P`/`-L`/`-e`/`--`),
  reject flag-only and multi-operand forms, accept only statically
  unquotable word/string/raw_string targets (no concatenation, ANSI-C
  quoting, backslash escapes, or expansions), and fail closed when the
  target is missing or not a directory.
- Probe git discovery more faithfully: resolve symlinks (realpath), treat
  a directory that is itself a git directory (bare repos, submodule
  storage) as a repo, fail closed when the search-depth budget exhausts,
  decode config values and subsections the way git does (quoted-segment
  concatenation, escapes), and add diff.<driver>.command and
  core.alternateRefsCommand to the program-valued keys.
- Scope fixes: fall back to the scoped execution root when the memory
  agent shell probe has no cwd; resolve compound-command defaults against
  the full command so a segment rule cannot override the cd-aware verdict;
  keep sub-commands after a directory change in the confirmation scope for
  both the shell and monitor tools.
- Tests: regression coverage for every fix plus mutation-checked wiring
  tests; skip the chmod-based EACCES simulation on Windows/root; use a
  relative submodule gitdir pointer; parametrize filter clean/smudge/process.

* fix(core): close round-2 review findings for git-config exec probe (#8645)

* fix(core): flag deprecated dot-form git config sections in exec probe (#8645)

* fix(core): probe core.hooksPath targets in git-config exec probe (#8645)

core.hooksPath was listed in PROGRAM_VALUED_KEYS, so any repo with the
key set — every husky/lefthook install, and the worktrees Qwen itself
creates — downgraded all whitelisted read-only git commands to ask.
The key names no program, it only redirects hook lookup, so resolve it
the way git does (~ expansion, relative anchored at the worktree root)
and probe the target directory for executable read-only-triggered hooks
exactly like the default hooks directory.

* fix(core): probe submodule storage configs in git-config exec probe (#8645)

* fix(core): tighten git config safety checks

* fix(core): address verification findings for git-config exec probe (#8645)

* refactor(core): reset git config probe to issue scope

* fix(core): use Git config semantics for read-only probes

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 23:12:14 +00:00
callmeYe
33b124321c
feat(core): support Qoder plugin extensions (#8661)
* feat(core): support Qoder plugin extensions

* fix(core): address Qoder extension review feedback

* fix(core): handle annotated tags and unsafe parse errors

* fix(core): harden Qoder conversion edge cases

* fix(core): sanitize Qoder conversion inputs

* fix(core): address Qoder extension round-3 review feedback

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

* fix(core): honor explicit marketplace selection over Qoder manifest

* fix(core): preserve nested plugin update provenance

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 22:35:54 +00:00
qwen-code-ci-bot
4d6246bd88
chore(release): v0.21.8 (#8757)
* chore(release): v0.21.8

* docs(changelog): sync for v0.21.8

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-08 19:25:22 +00:00
Shaojin Wen
6ddb0307ac
perf(review): bake a soft tool-call budget into finder and auditor briefs (#8708)
* perf(review): bake a soft tool-call budget into finder and auditor briefs

A fan-out wave's wall clock is its slowest agent, and the slowest agent is
reliably a wanderer: two measured runs of the same 14-agent wave took 11.7
and 41 minutes on comparable diffs, the difference being individual agents
spending 40-100 model calls exploring the tree while healthy agents settle
at 25-45 with indistinguishable findings.

plan.budget gains agentToolBudget - clamp(30 + effective/20, 30, 60),
computed and recorded like every other budget arm so no caller can inflate
it - and agent-prompt bakes it into every finder, chunk, invariant,
persona and reverse-audit brief. The verifier is exempt (verifyShard
governs its load, and its per-finding re-trace must not stop early), as is
Build & Test (deterministic commands).

The ceiling is soft and worded against the two failure modes a budget
invites: at the budget an agent stops EXPLORING, never reporting -
findings in hand are filed, open checks are disclosed as "did not get to"
into the same receipt machinery that judges whiffs - and the recall rule
is restated beside it so it cannot read as a reporting cap. A plan without
the field (older CLI) gets no ceiling: the fallback errs toward coverage,
matching the documented pre-budget behaviour.

* perf(review): declare budget on agent-prompt's local PlanReport

The CI build's tsc caught what vitest's transpile-only path did not: the
prompt builder reads plan fields through its own unknown-typed local
PlanReport interface, which had no budget member. Declared in the local
house style (unknown-typed) with explicit numeric narrowing in
toolBudgetBlock.

* perf(review): scope the tool budget per launch and close the disclosure loop

Round-1 review rework. The ceiling is now per LAUNCH, not per plan: a
chunk or invariant agent's allowance derives from its own territory
(launchToolBudget, same constants as reviewBudget), and every launch's
mandatory reads ride on top of the allowance instead of inside it, so a
whole-diff role on a huge diff is never exhausted by its assigned chunk
reads. Agent 8's whole-diff block now carries the budget too, and Agent 0
joins the exemptions — its mandatory work is issue-sized, not diff-sized.

The disclosure side becomes deterministic: briefs mandate the fixed
`Budget gap: <the check>` line format, coverageFromTranscripts parses
those lines into the report's budgetGaps (a NOTE, never a gate failure —
punishing disclosure teaches agents not to disclose), and SKILL.md's
Step 3D says how each gap is ruled: an incomplete required trace joins
unreviewedDimensions, optional depth goes to "Not reviewed".

Tests: launchToolBudget scaling/rides-on-top/garbled inputs, the runtime
shape of reviewBudget's return, per-launch numbers for chunk, invariant,
whole-diff and Agent 8 briefs, a full-roster exemption sweep, garbled
agentToolBudget values falling back to no ceiling, and budgetGaps
parsing with ok staying true.

* perf(review): make the tool budget plan-authoritative and give disclosures teeth

Round-2 review rework, three threads of it.

The number: launchToolBudget now takes the plan's recorded value and
clamps it into the budget's own band in both directions, and a scoped
launch's territory-derived allowance can only lower it, never raise it —
the plan is again the one number every launch answers to, under version
skew included. Reads estimates count the launch's whole reading list
(brief, diff pages, the reverse auditor's findings file, an invariant
agent's file paging scaled by its added lines), garbled chunk entries
degrade to the scoped floor instead of NaN or whole-diff headroom, an
UNCOVERABLE chunk gets no budget block beside its exact-receipt
instruction, and the exemptions are declared on the briefs
(budgetExempt) rather than hardcoded names — with the roster test
walking BRIEFS so a new role must declare.

The disclosure: one parser (budgetGapDisclosures in lib/budget.ts)
shared by every reader — markdown-tolerant, placeholder-dropping,
control-char-stripping, length- and count-capped. coverage collects
gaps inside the guarded record walk (idle/blind/superseded agents'
copied templates no longer count), narrows a disclosing agent's chunk
credit to the lines the harness saw it read (told-list credit was
making budget stops invisible to the gate), and retirement refuses to
count a gap-bearing return as a dry audit — an agent's admission that
it stopped can no longer retire the chunk that still owes the work.
compose-review renders every parsed gap into the body's "Not reviewed"
section mechanically — on the Approve too — as a disclosure channel
that never caps; the capping ruling stays with the orchestrator per
Step 3D.

Also: check-coverage's missing-chunks NOTE no longer tells operators to
paste --whole-diff ahead of role briefs (that double-budgets; the block
is Agent 8's alone), and its budget-gap NOTE puts the directives before
the agent-authored text.

* perf(review): unpunish disclosure, converge under it, and bound its blast radius

Round-3 review rework — 27 findings, two of them reversals of round-2
decisions, owned as such.

Reversal one: a disclosing agent's coverage credit is NOT narrowed.
rangeOf records only reads carrying a positive limit, so the narrowing
zeroed exactly the compliant offset-paged reader while an agent that
stopped WITHOUT disclosing kept full credit — an asymmetry that only
ever bites the discloser teaches agents not to disclose. The told
presumption is the same for every agent; a gap changes the Step 3D
ruling, never the arithmetic. This also restores the Uncoverable-claim
evaluation order the narrowing had broken.

Reversal two: a gap-bearing return is no longer blanket-unknown to the
reverse-audit retirement. The receipt is judged with its Budget gap
lines STRIPPED: a return whose only substance was its disclosures still
never retires, but a receipt substantive without them does — the
blanket rule made convergence impossible (a reverse auditor's ceiling
is routinely met) and ran every budgeted loop to the round cap.

The parser is now line-based: no cross-line prefix class (the multiline
regex measured seconds on ordinary pathological returns), same-line
capture only (a bare header no longer swallows the next line), fenced
code and blockquotes are quotation not use, non-answers are dropped in
any punctuation (`None.`), duplicates fold, the sanitizer covers C1 /
U+2028/29 / bidi overrides, wrappers strip only in pairs, truncation
cuts on code points. Gap supersession is gap-aware: only a GAP-FREE
relaunch silences a record's disclosures.

The body channel is bounded and inert: each gap rides through mdField
(no @-mentions, #refs, links or stray </details> from agent prose), the
sentence caps at five attributed items with an "and N more" tail, a gap
the orchestrator promoted into unreviewedDimensions is dropped here so
the body never says it twice, and a disclosed gap denies the
"no blockers" certification.

Reads estimates: invariant paging from fileLines (the plan DOES record
post-change length; added-lines was a 6x undercount on volume-heavy
files), the chunkless 3A reverse auditor counts its findings-list pages
(keyed on acceptsFindings), and chunk territory is source-weighted like
the plan allowance it mirrors — a lockfile chunk no longer out-earns a
source chunk. Discriminating fixtures pin all three past their floors.

* perf(review): close the disclosure format's language and boundary holes

Four follow-on review findings. The whole-diff reading list now counts
each chunk's PAGES, not a flat one per chunk — an oversized chunk's
isTruncated paging was being paid out of the analysis allowance, and
the fixture itself encoded the contradiction (40k chars = 2 reads in
the chunk test, 1 in the whole-diff test). The disclosure matcher
accepts the zh forms (预算缺口/不足/用尽, with either colon) — the
receipt regex next door accepts zh receipts by design, so a
zh-narrating auditor's budget stop was invisible to every consumer.
The retirement clause is cut at an INLINE disclosure marker before its
substance is judged — a one-line return put the disclosure after the
receipt separator where the line-based strip cannot see it, and the
clause capture absorbed the gap text as its own substance. And
launchToolBudget caps the TOTAL at 200: the reads term comes from the
same unchecked-cast plan as the allowance, and a garbled chars of 1e9
was rendering a forty-thousand-call brief around the clamp.

---------

Co-authored-by: verify <verify@local>
2026-08-08 15:20:23 +00:00
qqqys
d58474f651
feat(core): checkpoint long-running Goal evidence (#8465)
* feat(core): checkpoint long-running Goal evidence

* fix(core): reject blocked Goals on evidence exhaustion

* test(core): pin Goal checkpoint recovery contracts

* fix: retain Goal rejection feedback and dedupe checkpoint records (#8465)

Address the review findings on the Goal evidence checkpoint change:

- Keep the verifier rejection feedback when a follow-up checkpoint
  fails, so the resumed continuation still learns why the proposal
  was rejected.
- State the cumulative claim byte budget in the checkpoint verifier
  prompt so conforming output is steered inside the materialize gate.
- Suppress checkpoint-bookkeeping goal_state records in ACP transcript
  replay and CLI resume rendering, matching the live TUI display path.
- Pin the reviewed behaviors with mutant-killing tests and drop five
  unneeded runtime-options casts.

* fix: make Goal replay suppression and checkpoint recovery cause-aware (#8465)

* fix: persist goalCause in transcript replay state across page handoffs (#8465)

The cause-aware bookkeeping suppression only worked within one replay
machine: the persisted replay state dropped goalCause, so the first
goal_state record after a page boundary re-emitted the duplicate
bookkeeping card the check exists to suppress. Persist goalCause in
TranscriptReplayStateV1 (snapshot, parse, constructor) and thread it
through the page plumbing and the backward-pagination seed.

Also align parseClaim with materializeGoalEvidenceCheckpoint on the
shared claim limit: trim before measuring and count code points, so a
max-length claim with trailing padding no longer sinks the checkpoint
side query.

* fix(core): harden Goal evidence checkpoint gates and wire parsing (#8465)

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

* fix: settle post-commit Goal checkpoint failures and tighten evidence gates (#8465)

* fix(core): resolve goal checkpoint review findings (#8465)

* fix(core): align goal checkpoint byte budget with enforcement (#8465)

Count only claim text against the checkpoint byte cap so the budget the verifier prompt advertises matches what materialization enforces, share the proof-kind guard with the persisted-record parser, and restore reference_not_catalogued coverage.

* test(core): pin unknown-source checkpoint rejection message (#8465)

* fix(core): bound goal checkpoint windows and soften verifier failures (#8465)

---------

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>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 10:14:46 +00:00
jinye
59b750fc4d
feat(serve): Expose active work state (#8588)
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
* feat(serve): expose active work state

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

* refactor(serve): rebuild active-work reporting on channel-wide snapshots

Reworks the active-work signal after review. Three changes of substance.

Drops the 45s heartbeat watchdog entirely. It inferred "this channel is
dead" from "one Session stopped reporting" and killed the whole channel,
taking every Session on that process with it — including on a suspend,
a long event-loop stall, or a single dropped notification. Channel
liveness is a transport concern and gets its own mechanism.

Replaces the per-Session boolean with a channel-wide snapshot of named
holds, derived on every report from the owners of the work (the
registry's unfinalized set, the notification queue) rather than from a
ledger kept alongside them. Full snapshots make a dropped report
self-correcting in both directions, and a Session's absence from one is
positive evidence the child released it. Agent holds now use
hasUnfinalizedTasks()'s predicate, closing the cancel to
finalizeCancelled() window where a cancelled agent looked idle and its
terminal notification could be stranded.

Leaves prompts out of the child's report: the daemon accepts, queues,
dispatches, and settles them, so its own count is authoritative and
covers the FIFO wait the child cannot see. A snapshot is flushed ahead
of the prompt response so a hold the prompt left behind is on the wire
before the daemon drops that count.

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

* feat(serve): confirm idle before closing, and grade the health signal

Completes the active-work rework with the two facts a restart controller
was still missing and the one guarantee automatic cleanup was missing.

Automatic cleanup no longer destroys a Session on the strength of a
cached snapshot. It asks the child to close only if unheld, and the
child answers under its own close gate — with the gate held no prompt is
admitted and no automatic turn starts, so a hold cannot appear between
the check and the teardown. A refusal hands back the current holds and
the daemon adopts them. An unanswered request is neither retried nor
assumed: the Session stays, and the next snapshot settles it, because a
Session absent from one has provably been released. Every automatic path
— detach, attach rollback, prompt settle, notification settle, a child
reporting itself idle — now funnels through one decision point instead
of four near-copies.

Health gains activeWorkReporting and activeWorkStaleMs. Without them
activeWork:false cannot be told apart from "no child told me anything",
which is the one case where acting on it is unsafe. Freshness is graded
by the daemon rather than the controller, since the cadence is negotiated
per channel; a stale snapshot or a child omitting a category degrades the
grade instead of silently narrowing what the boolean covers.

Tests: acp-bridge 489/489, acpAgent 383/383, Session 534/534,
serve suites 1188 with one pre-existing cross-file flake in the Live
Appshot integration tests (reproduces on the unmodified tree, failing a
different test each run).

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

* fix(serve): contain snapshot-collection failures, and repair two Session mocks

CI caught two things the local runs missed.

The reporter's snapshot construction was unguarded. Only the send was
wrapped, so a throw while collecting a Session's holds escaped through
setInterval and queueMicrotask as an uncaught exception — capable of
taking down the ACP child — and through flush() into the prompt path,
turning a reporting problem into a failed prompt. Collection is now
wrapped and a failed snapshot is abandoned whole rather than sent
partially: a Session missing from a report reads as released, and one
reported with no holds reads as safe to close, so publishing a partial
snapshot would actively invite the daemon to destroy live work. Sending
nothing lets the daemon's copy age instead, which its freshness grading
already treats as untrustworthy and retains. flush() no longer rejects.

Session.review-lease and Session.worktree mock the background-task
registry without setStatusChangeCallback, so constructing a Session threw.
That break arrived with the original commit, which verified only
Session.test.ts; the sibling Session.*.test.ts files were never run. Both
mocks now carry the methods the constructor and the hold collector need.

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

* test(serve): prove the reporter contains collection and transport failures

The previous commit added the guard but could not have demonstrated it:
the same commit also gave the acpAgent Session mock a
collectActiveWorkHolds, removing the very condition that triggered the
throw. The unhandled error disappearing was therefore explained by the
mock alone, and active-work-reporter.ts had no tests at all.

These cover the escape routes that matter — the interval timer, the
coalescing microtask, and flush() on the prompt path — plus the choice to
abandon a whole snapshot rather than send a partial one, since a session
omitted from a report reads as released and one reported with no holds
reads as safe to close.

Verified by removing the guard: five of the nine fail with the collection
error escaping, and pass again once it is restored.

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

* fix(serve): make every automatic teardown ask before destroying

Self-review of the previous revision found that this PR had promoted a
cached child report from a hint into the authority that permits destroying
a Session. Four teardown paths consulted it, their guards disagreed with
each other, and each was weaker than what main had. The four are one
defect with four exits, so they are fixed as one change.

Absence from a snapshot no longer authorizes teardown. Because reports
are complete, a Session the child omits holds nothing on the child side —
so absence and reported-with-no-holds are the same fact and now take the
same path. The separate absence loop is gone; it lacked the subscriber
and client guards `maybeCloseIdleSession` applies, so one snapshot could
destroy a Session with a live SSE subscriber and a registered client.
That contradicted this PR's own claim that an unreported Session is
retained, and the old test asserted the destruction. Both are corrected.

A conditional close is now marked in flight across the whole confirm-then-
teardown span, and attach, prompt, and rewind refuse a Session in that
state exactly as they refuse one already closing. `closeSessionImpl` sets
`closing` synchronously, but the round trip in front of it is an await of
up to ten seconds; on main the guard sequence ran straight into teardown,
so splitting it is what opened the window.

A snapshot older than the freshness window stops counting as evidence.
Staleness was already computed, but only to grade health, never to gate
destruction — so a child that went quiet after one empty report left a
cache that permitted reaping indefinitely. Never-reported and gone-quiet
now land in the same retained bucket. Reclaiming a channel that has truly
stopped answering belongs to transport liveness, not here.

The idle reaper asks the child too. Its TTL says the client stopped
caring, which is not the same as the child having nothing left to run.

Health coverage is exposed as counts and graded once daemon-wide, because
grades do not compose: a runtime with zero Sessions is vacuously `full`,
and folding that in let an empty workspace vouch for another workspace's
unreported Sessions. `activeWorkStaleMs` now measures only covered
Sessions, so it can no longer report positive staleness beside a grade
saying nothing is covered.

Also: bound snapshot `sessions[]` and `holds[]` so a buggy child cannot
make the daemon walk an unbounded structure per report, and retract the
background-task status callback by identity rather than blanking a
single-slot setter the TUI also uses.

Tests: the absence test now asserts retention under a registered client
and under a live subscriber; new regressions cover the recovered lost
close response, the stale-snapshot gate, admission refusal during a
conditional close, the reaper's confirmation, the oversized-snapshot
discard, and the mixed empty/uncovered health aggregate.

* fix(serve): make unknown a reason to ask, not a reason to skip

Triage review found that the design doc, the PR description, and the
comment on `entryHasActiveWork` all promised the daemon *asks* the child
about a Session it has not heard about, while no code path ever did:
`entryHasActiveWork` returns true when the child's side is unknown, and
the cleanup path returned early on exactly that. The finding predates the
guard rework and survived it unchanged.

Skipping on unknown looks like the safe direction and is in fact the worse
failure. Nothing resolves it — a Session on a channel that went quiet is
retained forever, and the idle reaper skips it too, so there is no path
out at all. Asking resolves it definitively: the child answers under its
own close gate whether or not its snapshots are arriving, the round trip
is bounded, and every non-answer still retains.

So the predicate is split by what it actually knows. `childReportsHeldWork`
is positive knowledge only; `childWorkIsUnknown` is the absence of a
gradeable report. The health surface ORs both, because a controller must
never read "nobody told me" as "nothing is running". Automatic cleanup
blocks only on known work and lets unknown through to `confirmChildUnheld`.

Also moves `parseActiveWorkSnapshot` out from between two import blocks
(pure relocation, no logic change) and aligns the doc wording, including
the shared-guard table, with what the code now does.

* fix(serve): close three teardown races the confirm window opened

Review found three ways the conditional close can still destroy a live
Session. All three share a cause: the round trip turned a synchronous
guard-then-teardown into an awaited span, and three things that were
previously impossible to observe mid-teardown now are.

**A restore in flight looks exactly like an abandoned Session.**
`session/load` registers the entry before awaiting `artifacts.restore()`
and `seedSessionUpdates()`, and registers its first client only after —
so for that whole window there are no clients, no subscribers, nothing
held, and the child answers the conditional close truthfully. The
snapshot trigger this PR added fires inside it. Excluded in
`entryIsAutoCloseCandidate` rather than at the snapshot trigger, so the
reaper's TTL elapsing inside a slow restore is covered too.
`pendingRestoreIds` already existed but was read only by
`hasNoChannelWork`, never by the close funnel.

**Teardown re-resolved the target by id without re-checking identity.**
`closeSessionImpl` does a fresh `byId.get`, and the id can be
re-registered to a different entry during the round trip: an explicit
kill removes this one (kill ignores the in-flight flag by design, keeping
its force semantics) and a `session/load` for the same persisted id
registers a fresh one. The stale continuation then tore down the newly
restored Session under its just-attached client. One identity re-check
after the await.

**The restore path was not upgraded to the new admission predicate.**
`sendPrompt`, `rewindSession`, and single-scope attach check
`isClosingOrAuthorizingClose`; `restoreSession` still checked bare
`closing` at both its guards, so a client could attach inside the window
and lose the session under it. That directly contradicted the
`closeIfChildUnheld` comment claiming every admission path checks the
flag. Its `racedEntry` branch had no closing guard at all — a narrower
pre-existing hole, same defect, same predicate.

Regression test covers the restore-path admission refusal. The other two
need a mid-restore snapshot and a kill-then-reload interleave that the
mocked-channel harness cannot stage honestly; both are pinned by reading
the code paths, which is weaker and worth saying.

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 06:34:20 +00:00
Nothing Chan
761272542d
fix(core): use Singapore Token Plan DeepSeek model id (#8650) (#8705)
Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local>
2026-08-08 05:48:03 +00:00
qqqys
88a325bce9
feat(workflows): add cooperative pause and resume (#8320)
* feat(workflows): add cooperative pause and resume

* fix(workflows): restrict pause to background runs

* fix(cli): clarify foreground workflow pause errors

* fix(core): preserve dispatch errors across cancellation

* test(core): cover late workflow state callbacks

* fix(workflows): address review suggestions (#8320)

- Rename misleading `terminal` local to `presentation` in BackgroundTasksDialog
- Fix vacuous `toContain('p')` assertion to `toContain('Background tasks + p')`
- Fix vacuous gate assertion with macrotask yield in scheduler test
- Add over-count cap test for `onAgentCompleted` past dispatched count
- Add pausing-state approval parking test
- Remove dead `concurrencyLimiter` module (no production consumers)

* test(workflows): pin review-flagged mutation-surviving branches (#8320)

* test(cli): use valid agent status in detail-view reset test (#8320)

* test(workflows): harden pause-gate settle probes with a full flush (#8320)

* fix(workflows): address round-5 review findings (#8320)

* test(ci): sync review timeout assertions with repository variables (#8320)

* fix(workflows): address round-6 review findings (#8320)

* fix(workflows): address round-7 review findings (#8320)

* fix(workflows): address round-8 review findings (#8320)

* fix(workflows): address round-9 review findings (#8320)

* fix(workflows): address round-10 review findings (#8320)

* fix(workflows): address round-11 review findings (#8320)

* fix(workflows): address round-12 review findings (#8320)

* fix(workflows): address round-13 review findings (#8320)

* fix(workflows): address round-14 review findings (#8320)

* fix(workflows): address round-15 review findings (#8320)

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 04:21:21 +00:00
callmeYe
52f4fbe0b3
feat(web-shell): install Extensions from archives (#8621)
* feat(web-shell): install extensions from archives

* fix(web-shell): harden extension archive uploads

* test(cli): align archive failure bridge assertion

* fix(extensions): address archive upload review feedback

* fix(webui): stagger extension archive upload timeout

* fix(extensions): address archive upload round-4 review feedback

* fix(extensions): address archive upload round-5 review feedback

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 02:08:52 +00:00
destire-mio
b34a08d16f
fix(core): separate hook context from transcript display (#7948)
* fix(core): separate hook context from transcript display

* test(ci): gate desktop transcript projection

* revert: keep desktop CI scope unchanged

* test: cover transcript display fallbacks

* fix(transcript): address review feedback

* fix(transcript): reconcile post-merge provenance paths

* fix(webui): preserve legacy transcript concatenation

* test(transcript): cover projection consumers

* fix(transcript): consolidate hook context projection

* fix(transcript): support single-field display provenance

* fix(transcript): strip hook context with invalid metadata

* test(acp): cover empty replay display text

---------

Co-authored-by: destire-mio <248462155+destire-mio@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-08 02:03:20 +00:00
Nothing Chan
5859daf07f
fix(core): preserve timeout retry metadata (#8531)
* fix(core): preserve timeout retry metadata

Keep sanitized transport metadata on enhanced timeout errors so safe pre-content retries can classify them correctly, while preserving HTTP status authority.

* fix(core): mark bare SDK timeouts as retryable transport (#8527)

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

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-08 01:54:26 +00:00
Shaojin Wen
92ff0a1363
perf(review): move remote matching into CLI (#8658)
* perf(review): move remote matching into CLI

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

* fix(cli): use exit 7 for match-remote multi-match, drop dead field (#8658)

* fix(cli): harden match-remote host resolution per review feedback (#8658)

- Strip an explicit port from the input host before remote comparison;
  a port-bearing GHE verdict host could never match its own remote and
  a same-repo review was demoted to lightweight mode
- Exit 1 only when git itself fails: a bare repository now resolves
  remotes like any other checkout, matching the documented contract
- Inherit an operator-exported GH_HOST when --host is absent, the same
  resolution submit uses, so bare PR numbers on GHE clones match
- Write the machine-read stdout line with loud writeStdoutLine so a
  failed write exits non-zero instead of exiting 0 with empty output
- chdir out of the temp dir before rmSync in match-remote.test.ts
  (Windows locks a directory that is the process cwd)
- Pin both SKILL.md match-remote hunks in SKILL.test.ts so reverting
  either path to model-prose matching fails a test
- Correct the design doc's prompt-size accounting and host-resolution
  paragraph; label the e2e pointer as an untracked run archive

* fix(cli): match partial-clone remotes and unify host resolution (#8658)

* fix(cli): thread resolved GHE host through bare-number remote matching (#8658)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 01:47:32 +00:00
Nothing Chan
0701b76b87
fix(core): refresh MCP session metadata without reconnecting (#8522)
* fix(core): refresh MCP session metadata in place

* fix(core): isolate MCP metadata refresh

* fix(core): harden MCP session metadata key and refresh (#8522)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-08 01:33:41 +00:00
Zqc
845d6cf77e
fix(memory): Refresh live instructions after memory writes (#8640)
* fix(memory): refresh live instructions after memory writes

* fix(memory): refresh ACP context memory writes

* fix(memory): keep context refresh intent for marked turns

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
2026-08-07 17:02:49 +00:00
Harjoth Khara
efc7ec7a85
fix(core): record the delivered prefix when a transport cut is continued (#8624)
* fix(core): record the delivered prefix when a transport cut is continued

After a socket cut mid-response, the continuation attempt resumes from
the text the user already saw. `prependTextToLastModelTurn` merges that
prefix back into the trailing model turn, but it writes `this.history`
and nothing else. The JSONL transcript keeps only the resumed remainder,
so `--resume` and `--continue` rehydrate a turn that starts mid-sentence
while the live session shows a coherent answer.

Merge the prefix into the assistant record as it is built, reusing the
same overlap dedup `prependTextToLastModelTurn` uses, so one turn goes in
and one matching turn lands on disk.

The merge belongs at the record build, not next to the history merge:
the record is appended from inside `processStreamResponse`, before the
outer send loop regains control, and `appendRecord` is append-only — a
second record written afterwards would sit behind the remainder and
resume would read the halves out of order.

It is gated on success. When the attempt fails, the record has to keep
matching the remainder-only partial that survives in history, and a
fresh-restart retry discards the prefix from history via
`resetTransportContinuation`. The prefix rides as a per-attempt argument
rather than instance state, so no stash can dangle into a later turn.

Refs: #8094

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* test(core): await the rejecting continuation stream before advancing timers

The tool-call-cut test drained its stream with
`collectStreamWithFakeTimers`, which returns the collecting promise only
after advancing timers. That send rejects during the advance, so the
rejection sat unhandled for a tick — vitest caught it as an unhandled
error and exited 1 with every test still passing.

Attach the assertion first, then advance, matching the shape
`expectStreamExhaustion` in this file already uses for the same reason.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* fix(core): merge the delivered prefix once, before either durable write

Addresses review findings R1-1, R2-2 and R1-2 on #8624.

The first version merged the prefix into the JSONL record only, at the
point the record was built, and left history to the outer send loop's
`prependTextToLastModelTurn`. Two expressions, two write times — and both
diverged.

R1-1: the record deduped against `contentText`, which is trimmed, while
history deduped against the raw part. A cut landing on a token boundary
recorded "The result is" + " 42." as "The result is42.", and a 6-byte
overlap that is significant only untrimmed recorded "The grand
totaltotal sum is 9.".

R2-2: the record was appended before the history push, and a tool-result
continuation yields a deferred finishReason chunk after it. A consumer
abandoning iteration there — an abort inside `Turn.run` — left a merged
record against a remainder-only history, permanently, since the JSONL is
append-only.

Fold the prefix into `consolidatedHistoryParts` instead, once, after
stream validation and before either write. The record and the history
push are then built from the same parts, so they cannot disagree about
whitespace, dedup, or timing. The outer merge is gone, along with
`prependTextToLastModelTurn`, and the merge itself is now one shared
helper (R1-2) rather than a hand-written expression per site.

Not re-run in the outer loop on purpose: the dedup helper only strips a
replayed prefix that clears its significance floor, so a short prefix
would survive a second pass and be doubled.

Refs: #8094

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

* docs(core): keep getRecoveryContinuationSuffix's doc with its function

The new helper was inserted between that JSDoc block and the function it
documents, so the block silently reattached to the wrong function.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 16:57:54 +00:00
jinye
26352fcc6a
feat(external-context): Add optional Mem0 memory writes (#8507)
* feat(external-context): Add optional Mem0 memory writes

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

* fix(hooks): Preserve confirmation content visibility

Render PreToolUse confirmation reasons literally and keep long confirmations accessible through the virtualized TUI. Add unit and interactive regression coverage for Mem0 write confirmations.

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

* fix(external-context): Address memory write review findings

Align Hook and MCP argument handling, distinguish definitive Provider rejections from ambiguous outcomes, improve deployment diagnostics, and document the write-back trust boundary.

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

* fix(hooks): Refine plain-text confirmations

Render URLs consistently, avoid persistent virtual viewport gaps, and document the literal-rendering and managed deployment boundaries.

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

* fix(external-context): Support Auto Edit write confirmation

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

* fix(external-context): Harden write confirmations

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

* fix(cli): Measure virtual row height directly

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

* fix(cli): Preserve YOLO Hook confirmation content

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>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-07 16:52:33 +00:00
Shaojin Wen
b40719a8bb
fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap (#8631)
* fix(cli): Run ACP agent fan-outs concurrently and past the tool-call cap

The daemon's ACP session executed tool batches differently from the
core scheduler in two ways that broke long agent fan-outs such as
/review:

runBounded — the runner for concurrent batches — forced the first
three calls of any batch larger than the invalid-params threshold to
run one at a time, then clamped the rest to concurrency 3, although
agent calls are concurrency-safe and core's runConcurrently runs them
at QWEN_CODE_MAX_TOOL_CONCURRENCY (default 10). A /review fan-out of
10-15 agents therefore ran almost serially. Agent-only batches now
skip the serial prefix and the clamp: an invalid agent call fails in
build() before any side effect, so the concurrent loop's
near-threshold check still catches invalid-params loops just as fast.

The per-turn tool-call cap halted unconditionally at
model.maxToolCallsPerTurn (default 100) while core's
LoopDetectionService treats the default as adaptive — past the soft
cap a productive turn (diverse calls, no repetition) continues until
a stuck-repetition signal or the hard backstop (soft cap x 10). A
/review orchestrator needs well over 100 calls, so every high-effort
review under qwen serve died mid-review at call 101. The daemon now
mirrors core's checkTurnToolCallCap semantics, reusing the same
thresholds.

Measured on two high-effort /review runs (PRs #8522 and #8529): the
baseline died at call 101 after ~8.3h each; after this fix both
reviews run to completion in 4.4h / 5.3h, first fan-out wave 85m ->
33m, reverse-audit rounds 63-86m -> 25-35m.

* fix(cli): regenerate settings schema after maxToolCallsPerTurn doc update (#8631)

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

* fix(cli): Gate the daemon repeat halt on skipLoopDetection like core

* fix(cli): Address ACP fan-out review: keep wide-batch results, shared cap predicate (#8631)

- runBounded no longer aborts in-flight calls when loop detection fires
  in the capped race branch: wide batches keep in-flight results and only
  skip the unstarted tail, matching narrow-batch behaviour (nothing
  executed is discarded either way).
- Extract shouldHaltOnTurnToolCallCap from core's checkTurnToolCallCap
  and call it from the daemon guard so the two runtimes share one halt
  predicate and cannot drift.
- Hoist canonicalToolName into tools/tool-names.ts beside
  ToolNamesMigration; scheduler, loop detection, plan redaction and
  memory refresh now share the single alias resolver.
- Correct the wrong-direction cap wording (the daemon undershoots an
  explicit cap / hard backstop — the batch check runs before execution;
  the adaptive soft cap is exceeded by design up to the backstop) in the
  daemon comment, settingsSchema.ts (schema regenerated) and settings.md,
  and scope the always-on-guard sentence to core-client sessions.
- Tests: adaptive hard backstop, wide-batch loop tail skip, wide-batch
  keep-results, provider-duplicate counter exclusion, `task`-alias
  fan-out, getToolCallRepeatKey alias/key-order coverage; raise the
  fan-out concurrency deadline off the 2s wall clock.

* fix(cli): Address review: complete loop-guard docs, pin test envs, drop dead export (#8631)

* fix(cli): Address review: correct parity comment, pin halt semantics, test cross-response repeats (#8631)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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-07 16:32:44 +00:00
DDDonut
70633df8e0
fix(cli): time out silent MCP SSE startup (#8555)
* fix(cli): bound MCP transport startup

* fix(mcp): reuse connection timeout helper

---------

Co-authored-by: daleselaji-dev <265319989+daleselaji-dev@users.noreply.github.com>
Co-authored-by: daleselaji-dev <daleselaji-dev@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-07 15:41:59 +00:00
Dragon
adec1ea50f
feat(core): share compression cache with Gemini and Vertex AI (#8425)
* feat(core): share compression cache with Google GenAI

* fix(core): preserve restored compression accounting

* fix(core): preserve estimated compression accounting

* fix(core): preserve estimated token provenance on resume

* fix(core): harden compression provenance flow

* fix(core): keep compression counts conservative

* fix: preserve compression token provenance

* fix(web-shell): preserve estimated context usage

* fix(core): require provider-reported anchor for compression cache sharing

An estimate-derived token count misses the ~15-20K system/tools overhead
the shared compression request carries, so a magnitude-only anchor gate
could approve a shared request that overflows the context window. Gate
cache sharing on a provider-reported count, keeping estimate-only
sessions on the cold path until provider usage arrives.

Pin the zero-baseline end-to-end composition (derived baseline reaches
the service, missing anchor routes to the cold side query), repair the
garbled R3.4 test rationale comment, and log estimate-clamp padding.
2026-08-07 08:54:49 +00:00
Dragon
6897ef7440
feat(core): share compression caches with OpenAI providers (#8418)
* feat(core): share compression cache with OpenAI providers

* chore: refresh generated settings schema

* fix(core): scope compression cache marker to OpenAI

* test(core): cover OpenAI cache guards

* fix(core): tighten OpenAI cache-sharing contracts

* test(core): pin OpenAI prompt cache guards

* fix(core): centralize prompt cache sharing gate

* test(core): assert absent cache sharing marker

* fix(core): partition OpenAI cache keys for subagents

* fix(core): cover default OpenAI cache endpoint

* fix(core): preserve prompt cache identity for forks

* fix: avoid OpenAI cache fields on DashScope default
2026-08-07 07:04:57 +00:00