mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-12 10:15:31 +00:00
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 (
|
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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 ( |
||
|
|
73c064b44e
|
fix(core): exclude hook context from auto titles (#8758) (#8781)
Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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, '&')` to `.replace('&', '&')` 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) |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
761272542d
|
fix(core): use Singapore Token Plan DeepSeek model id (#8650) (#8705)
Co-authored-by: nothing <nothing@U-DQY4PXFJ-0222.local> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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 |