Skip to content

refactor(cli): remove unused EnumSelector component - #15

Closed
qqqys wants to merge 412 commits into
mainfrom
simplify/enum-selector
Closed

refactor(cli): remove unused EnumSelector component#15
qqqys wants to merge 412 commits into
mainfrom
simplify/enum-selector

Conversation

@qqqys

@qqqys qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Owner

What this PR does

Removes a UI selector component that is no longer referenced anywhere in the codebase, together with the test and snapshot that existed solely to cover it. The component renders a left-right value picker for enum-typed settings, but no screen or component mounts it; its only callers were its own unit test and snapshot. Nothing else changes.

Why it's needed

Dead surface is a cost: every contributor who reads it has to work out what wires it up, and the answer here is "nothing". The component arrived with an upstream sync and was never referenced in this repository's history — no production code, no string-keyed registry entry, no dynamic import path. Its dedicated test and snapshot exist only to cover the component itself, so they go with it.

Reviewer Test Plan

How to verify

This is a pure deletion of code nothing references. Verify the absence of consumers rather than behavior:

  1. rg -n '\bEnumSelector\b' packages integrations integration-tests scripts .github docs-site — expect zero hits after this PR (the only three before it were the component, its test, and its snapshot).
  2. npm run build && npm run bundle && npm run typecheck — all green locally with this change (a type-only break from a removed export would surface here; typecheck does not run in CI).
  3. npm run lint:ci — green locally with this change.

No user-visible behavior changes, so no before/after TUI evidence applies.

Evidence (Before & After)

N/A — non-UI change (dead-code deletion; nothing rendered this component).

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux

Environment (optional)

Local npm run build && npm run bundle && npm run typecheck and npm run lint:ci in a clean worktree off origin/main.

Risk & Scope

  • Main risk or tradeoff: essentially none — the component has zero production consumers, verified by full-corpus search including string keys, dynamic-import shapes, build graphs, and the test corpus; if a consumer were ever found, reverting the single commit restores everything.
  • Not validated / out of scope: macOS/Windows builds (no platform-specific code involved); no integration test run (no CLI behavior is touched by removing unmounted code).
  • Breaking changes / migration notes: none; nothing exported from any package surface was consumed externally.

Linked Issues

Part of the dead-surface cleanup tracked in #14.

Machine details (find-simplifications)
  • id / class: enum-selector — class 2, dead component with its test and snapshot.
  • Surface removed: packages/cli/src/ui/components/shared/EnumSelector.tsx (87 lines), EnumSelector.test.tsx (152 lines), __snapshots__/EnumSelector.test.tsx.snap (9 lines) — 248 lines deleted, 0 added (git diff --numstat).
  • Every consumer found and its kind: full-corpus grep (all extensions, including tests, snapshots, docs, .github, .husky, .vscode, patches, root build/lint manifests, tracked .qwen files) resolves to exactly three paths — the component itself (declaration), its test (test-only), and its snapshot (snapshot). Zero production consumers; zero string-keyed references (checked string-keys, build-graph, generated, vi-mock, mirrors, dyn-import, cli-flags, assets checklist rows); no integration-test reference (second pass without test exclusions); no design-doc or plan mentions it.
  • Proof steps run (survey protocol §3): ledger check (no tombstone), recency (introduced via the 2025-10-23 Gemini-CLI v0.8.2 sync per GitHub full-history path dating — local clone is shallow; no commit in the repo's history ever referenced the symbol), published-surface escape (territory is packages/cli/src — landable, not packages/core/channels/SDK/acp-bridge), full-corpus grep, own-file check (no in-file caller beyond the declaration), hidden-consumer checklist, test-only check, unwire history (no binding text ever existed — it was never wired), design-doc ownership (none).
  • Re-verified at land time against fresh origin/main (4a281f2efc): same three paths, no commits touching the symbol since the survey, branch base freshly fetched.
  • Verification: npm run build && npm run bundle && npm run typecheck green; npm run lint:ci green; targeted vitest skipped by design (the deletion carries the surface's only test; corpus re-grep on the landing checkout returned zero surviving references). Self-audit: two clean passes.
  • No collateral: no eslint.legacy-filenames.mjs entry (.tsx is outside the kebab-case rule), no i18n locale keys (component took display strings via props), no docs rows.
中文说明

本 PR 做了什么

删除一个在代码库中已无任何引用的 UI 选择器组件,以及仅为覆盖它而存在的测试和快照。该组件渲染一个用于枚举类设置项的左右切换选择器,但没有任何界面或组件挂载它;它唯一的调用方就是它自己的单元测试和快照。除此之外没有任何其他改动。

为什么需要

死代码是一种成本:每个读到它的人都得弄清是什么在用它,而这里的答案是“没有任何东西”。该组件随一次上游同步进入本仓库,在整个历史中从未被引用——没有生产代码、没有字符串键注册项、没有动态导入路径。它专属的测试和快照只为覆盖组件本身而存在,因此一并删除。

评审者测试计划

如何验证

这是一次对无任何引用代码的纯删除。请验证消费者的缺失,而不是行为:

  1. rg -n '\bEnumSelector\b' packages integrations integration-tests scripts .github docs-site —— 本 PR 之后应无任何命中(之前仅有的三处即组件本身、其测试与其快照)。
  2. npm run build && npm run bundle && npm run typecheck —— 本地在包含本改动的情况下全部通过(删除导出可能引发的纯类型问题会在这里暴露;typecheck 不在 CI 中运行)。
  3. npm run lint:ci —— 本地在包含本改动的情况下通过。

由于没有用户可见的行为变化,无需前后对比的 TUI 证据。

证据(改动前后)

N/A —— 非 UI 改动(死代码删除;该组件从未被渲染)。

测试环境

OS 状态
🍏 macOS ⚠️ 未测试
🪟 Windows ⚠️ 未测试
🐧 Linux ✅ 已测试

环境(可选)

在基于 origin/main 的干净 worktree 中本地运行 npm run build && npm run bundle && npm run typechecknpm run lint:ci

风险与范围

  • 主要风险或权衡:基本没有——该组件零生产消费者,已通过全语料库搜索(含字符串键、动态导入形态、构建图与测试语料)验证;万一未来发现消费者,回滚这一个提交即可完整恢复。
  • 未验证 / 超出范围:macOS/Windows 构建(不涉及平台相关代码);未运行集成测试(删除未挂载的代码不触及任何 CLI 行为)。
  • 破坏性变更 / 迁移说明:无;没有任何从包对外表面导出的内容被外部消费。

关联 Issue

属于 #14 跟踪的死代码清理工作。

机器细节见上方英文 “Machine details (find-simplifications)” 折叠块:候选编号 enum-selector(第 2 类:死组件连同其测试与快照),共删除 248 行、新增 0 行;全语料库搜索证明消费者恰为组件自身、其测试与其快照三处;调查协议各步骤及落地前的重新核验均已执行;构建、打包、类型检查与 lint:ci 全部通过;因删除本身带走了该表面唯一的测试,按流程跳过定向 vitest,改以落地检出上的语料库复扫(零残留引用)兜底;无允许列表条目、无 i18n 键、无文档行等连带清理项。

callmeYe and others added 30 commits August 19, 2026 06:42
* feat(daemon): add batch extension activation APIs

* fix(daemon): focus extension batch API on V2

* fix(sdk): export extension batch types

* fix(core): reject empty extension batches

* test(extensions): strengthen batch regression fences

* feat(extensions): allow batch activation declarations

* fix(extensions): preserve legacy activation declarations

* fix(extensions): reject ambiguous legacy identities

* fix(extensions): preserve batch activation lifecycle

* feat(extensions): key batch activation by name

* fix(extensions): harden batch activation lifecycle

* fix(extensions): preserve declared activation lifecycle

* fix(extensions): reconcile renamed and legacy policies

* fix(extensions): preserve renamed artifact lifecycle

* fix(extensions): validate persisted artifact paths

* fix(extensions): preserve re-keyed artifact directory
QwenLM#9464)

The "Record changed test files for the flakiness gate" step runs its
`git diff 'HEAD^1' HEAD` inside the env -i scrubbed child. On the
persistent pool, resolving the `^1` parent there intermittently fails
with "Could not access 'HEAD^1'" — the shallow merge-ref object store is
left unreadable by prior `--depth=2` fetches — which takes down the whole
verify lane.

The "Pin agent inputs" step already captures the base OID to
$RUNNER_TEMP/verify-base-oid while .git is still root-owned. Diff against
that content-addressed OID instead of re-resolving the parent: it needs
no parent walk and is the same value the workflow already trusts for its
post-build re-pin. Add a pin asserting the record step reads the recorded
OID rather than re-deriving HEAD^1.
… stop (QwenLM#9435)

The ACP daemon Session was the only duplicate-provider-id circuit
breaker path (PR QwenLM#5657) that terminated silently: the turn ended as a
normal end_turn with nothing in the transcript and no telemetry, so the
session looked hung. Route the breaker through
recordDaemonLoopDetected with LoopType.GLOBAL_TOOL_CALL_DUPLICATE — the
same loop type the non-interactive CLI reports — so foreground turns
fail with the visible LOOP_DETECTED turn error, the context message is
preserved for the next turn, and the LoopDetectedEvent telemetry is
emitted. The bespoke repeatedDuplicateProviderToolCall result flag and
its dead consumer branch are removed in favor of the existing
loopDetected plumbing.
* feat(providers): add qwen3.8-max to Token Plan model list

The stable qwen3.8-max has graduated from preview and is listed in the
Bailian model catalog, but the Token Plan preset still only ships
qwen3.8-max-preview. Mirror the preview entry's capabilities (1M context,
thinking mandatory, image + video understanding) and pin the new entry in
the preset unit tests.

Closes QwenLM#9376

* test(core): pin qwen3.8-max generationConfig in Token Plan preset test (QwenLM#9383)

---------

Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com>
…ompress-fast (QwenLM#9331)

* fix(cli): prevent /rewind from dropping history after /compress-fast

computeApiTruncationIndex treated every successful compression marker as a
summarizing boundary. /compress-fast emits the same marker but removes no
user prompts from the API history and inserts no summary prefix, so rewinding
to a post-marker turn collapsed the truncation anchor to the startup context
and silently dropped the whole pre-marker conversation.

Tag compression UI items with the path that produced them
(CompressionProps.compressionKind) and make the rewind mapping ignore 'fast'
markers as boundaries. Legacy markers without a kind keep prior behavior.

Fixes QwenLM#9320

* fix(cli): exclude microcompact media placeholders from rewind prompt count

Microcompaction's media clearing replaces the top-level inlineData/fileData
parts of user entries with text placeholders. A media-only user entry (e.g.
an image-only ACP prompt) never produced a UI user turn, but once cleared it
satisfied isUserTextContent's naive text check, desynchronizing the API
prompt count from the UI turn count. Rewinding across a fast marker then
returned one prompt early and truncateHistory silently dropped a turn the UI
still shows — the same hazard this PR fixes, newly reachable through the
cross-fast-marker rewinds it enables.

Exclude placeholder-prefixed text parts from the prompt count (entries
mixing placeholders with real prompt text still count), and mirror the
compressionKind persistence caveat onto ChatCompressionRecordPayload.

Addresses review R1-1 (Critical) and R1-3 (minimal option).

* fix(cli): match full microcompact placeholder shape in rewind count

- R3-1: exclude media-clear placeholders by the full generated shape
  (`[Old inline media cleared: <mime>]`) instead of the bare prefix, so a
  genuine user prompt that merely begins with the prefix still counts as a
  prompt. Adds a shared isClearedMediaPlaceholder() helper next to the
  producer constant and regression tests for both the truncation-late and
  spurious-compressed-error witness cases.
- R3-2: document why the ACP session's private #isUserTextContent twin
  deliberately keeps the prefix check (ACP rewind maps against per-prompt
  file-history snapshots, which include media-only prompts).
- R3-3: drop the zero-consumer MICROCOMPACT_CLEARED_MESSAGE and
  MICROCOMPACT_CLEARED_IMAGE_PREFIX barrel exports added by the prior
  commit; export only the new isClearedMediaPlaceholder() helper.

* fix(core): match empty-mime media-clear placeholders in rewind count

sanitizeMimeForPlaceholder returns '' for empty/whitespace-only/
bracket-only mimeTypes and the producer's ?? fallback only covers
null/undefined, so microcompaction can emit `[Old inline media
cleared: ]`. The consumer regex required one-or-more mime chars and
rejected that shape, leaving a cleared media-only entry counted as a
genuine prompt and desynchronizing the rewind prompt count. Accept the
empty-mime shape and pin the producer/consumer round-trip for
degenerate mimeTypes.

Also correct the ACP-twin note in historyMapping.ts (the twin keeps
the bare text-presence check, not a prefix check) and document the
deliberate divergence reciprocally on Session.ts #isUserTextContent,
with a Session.test.ts pin that cleared placeholders stay rewindable
on the ACP side.

* docs(cli): document and pin exact-match placeholder collision in rewind mapping

Records the known exact-match collision corner next to the media-clear
placeholder exclusion and pins its fail-safe behavior with a test: a
genuine prompt whose entire text equals a generated placeholder is
excluded from the API prompt count, so later rewind targets abort
loudly (-1) instead of truncating against a misaligned count. A durable
fix needs a structural sentinel on cleared parts, which changes the
persisted API history shape and stays out of scope for this PR.

* fix(cli): tighten rewind placeholder match and pin mid-history collision

Address the round-5 review thread within the scope that is not pending
the maintainer structural decision:

- The media-clear placeholder regex no longer accepts \r/\n/\t interiors:
  sanitizeMimeForPlaceholder normalizes them to spaces, so the producer
  can never emit them, and accepting them let multi-line user text that
  merely starts with the prefix be misclassified as a cleared-media
  placeholder. Every producible shape (including the empty-mime one)
  still matches.
- The exact-match collision doc no longer claims "never silent history
  loss": mid-history, rewinding TO a colliding turn truncates one turn
  late (its prompt+response stays in model context while the UI removes
  the turn - under-deletion, not loss of context the UI keeps). The
  mid-history shape is now pinned next to the first-turn pin so a future
  structural sentinel updates both expectations.

* fix(cli): fail loud on rewind when marker-less auto-compaction left a compressed prefix (QwenLM#9437 entrance 3)
…LM#9433)

A named Agent Team teammate accepted run_in_background: false but
silently ignored it: the teammate route runs before the regular
foreground/background classification and always returns right after
spawn, so the flag had no effect. A structured local run showed five
named teammates spawned concurrently — including two launched with an
explicit false — each continuing to consume model tokens while the
accepted flag said not to background the launch.

Named teammates are inherently concurrent (persistent team identity,
messaging, automatic final-report delivery), so foreground semantics
cannot apply. Make the incompatible combination hard to generate and
impossible to spawn:

- Tool prose: the team guidance, the name property, and the
  run_in_background schema description now state that teammates always
  run concurrently, that callers should omit run_in_background, and
  that an inline blocking result requires omitting name and using a
  regular agent with run_in_background: false.
- validateToolParams rejects run_in_background: false when name routes
  to an active team, before any spawn. Without an active team the name
  falls through to a regular agent, where the foreground request stays
  valid.
- The execute-time team-routing branch carries the same rejection as
  defense in depth, mirroring the existing model/isolation blocks.

Fixes QwenLM#9430
…t is written (QwenLM#9460)

* fix(review): clamp the posting volume at its origin, not only where it is written

Follow-up to QwenLM#9413, from a maintainer's independent pass. The terminal
`VOLUME:` line was the one read site of the round's count that skipped
the shared volume reader: the serializer, the parser and the side-file
recovery all clamp, but `result.postedInline` carried the raw drafted-
comment length, so in the defensive over-cap case the line would print an
uncapped number beside a marker recording the capped one — the two-
outputs-disagree failure that reader's own docstring exists to prevent.
Clamping once where the count is derived puts every surface on the same
value. Not reachable with any real producer (no round drafts 100k inline
comments), so this closes a consistency gap rather than a live bug.

Also corrects a fixture comment that survived the round-4 semantics
reversal: `postedInline`'s absence is preserved, not defaulted to zero,
and the comment beside it still claimed the opposite of what the
validator, the type and two tests all say.

* docs(review): re-anchor the bodyTrim fixture comment to its own semantics

The rewritten `postedInline` comment broke what the line below it refers
to. The fixture's comments used to read as one chain — every field
non-default because absence defaults to something — so `bodyTrim`'s
"for the same reason" resolved correctly. With `postedInline` now
documenting the OPPOSITE reason (its absence is preserved, not
defaulted), the nearest antecedent teaches a maintainer that `bodyTrim`
is preserved too, while the validator defaults it and always emits it.
The comment now states its own side of the split instead of pointing at
whichever comment happens to precede it.
* feat(extensions): support authenticated HTTPS Git installs

* test(serve): update capability integration baseline
…M#9432)

The empty list_agents result ("No background agents are available in
this session") is true for the ordinary background-subagent roster but
reads as team status while named Agent Team teammates are active, which
can trigger duplicate launches or a false "no workers running"
conclusion.

Make the control-plane boundary explicit:
- tool description states that named teammates are NOT listed, deliver
  their final reports automatically, and that list_agents/task_list
  polling must not be used to wait for them;
- empty result names the roster it reports on and the teammate
  exclusion.

The registries stay separate; this is a tool-contract wording fix only.

Fixes QwenLM#9431
* feat(core): reject upstream fail-fast placeholder responses

Upstream endpoints occasionally fail fast with an HTTP 200 whose entire
body is the placeholder text '(request timeout)'. The response passes
all existing stream validation and gets persisted as a normal assistant
reply, polluting subsequent request context and surfacing to users as a
lost reply.

- Throw InvalidStreamError('UPSTREAM_DEGRADED_RESPONSE') at stream end
  when the whole response body is exactly the placeholder, reusing the
  transient retry budget so the turn is rolled back and re-attempted.
- Treat placeholder-only model turns as invalid in extractCuratedHistory
  so already-polluted sessions (including --resume) stop replaying them.

Both checks are exact whole-turn matches to avoid false positives on
legitimate mentions of the text.

Fixes QwenLM#8916

* fix(core): prevent placeholder text from reaching display before retry (QwenLM#8938)

- Defer the first chunk with a finishReason in non-continuation turns so
  post-stream validation (placeholder check, empty-text check) can reject
  it before consumers see the content.  Without this a single-chunk
  fail-fast placeholder (text + finishReason: 'STOP') was yielded to the
  TUI / headless adapter before the throw, and neither consumer rolled
  back the committed output.
- Add exhaustion test for UPSTREAM_DEGRADED_RESPONSE retry budget.
- Add curation test for whitespace-padded placeholder (.trim() guard).
- Add curation test for placeholder turn carrying a functionCall.
- Assert the placeholder text is absent from emitted stream events.

* fix(core): block degraded continuation leaks

* fix(core): catch split degraded placeholders

* fix(core): withhold degraded placeholder chunks

* fix(core): preserve current image payloads after placeholder curation

* fix(core): preserve deferred stream chunks safely

* fix(core): fold held placeholder-prefix text into continuation accounting

When a stream is cut while degraded-placeholder-prefix chunks are held
back (never yielded), the send loop's delivery accounting saw nothing
delivered and replayed fresh — letting a placeholder split across the
cut ('(request ' + 'timeout)') slip through as the harmless-looking
fragment. Stage the held text on the pipeline's error path and fold it
in the attempt's catch so the continuation gate resumes from the prefix
and the reassembled text is still rejected as a completed placeholder.
A completed placeholder is the exception: there is nothing honest to
resume from, so that recovery stays a fresh replay.

Fixes the split-placeholder regression test; all 332 geminiChat tests
green.

* fix(core): latch deferredFirstChunk so duplicate finishReason chunks are not dropped

A second finishReason-bearing chunk arriving before any yield overwrote
deferredFirstChunk (plain assignment at both deferral sites), so only the
slot's FINAL value was yielded post-stream while every overwritten chunk's
parts still landed in allModelParts -> history/JSONL: text persisted but
never displayed, or a functionCall persisted but never dispatched. Latch
the first deferred chunk with ??= at both sites (R8-1).

* fix(core): judge deferred chunk against accumulated text on stream error

The error-path escape check judged the deferred chunk by its own parts
only. A placeholder split into a held prefix and a finishReason-bearing
tail ('(request ' + 'timeout)') failed that per-chunk prefix check on
the tail alone: the fragment leaked to consumers while the held prefix
was staged after it, scrambling the continuation handoff. Judge the
deferred chunk against the accumulated attempt text — the in-loop hold's
own expression — and hand the combined text off in delivery order.

Also drop dead stores: the yieldedAnyChunk write after the loop's last
read and the trailing local resets after the final yields.

* fix(core): deliver withheld placeholder-defense chunks in arrival order, drop the invisible continuation handoff

Three review findings on the placeholder-defense machinery:

1. The deferred first chunk reached consumers out of arrival order — the
   post-loop flush yielded held chunks first, and any inline-yielded chunk
   preceded it, so displayed text diverged from arrival-order history/JSONL
   (and consolidation's in-place part merge rewrote the shared part the
   deferred chunk carried, duplicating text on delivery).

2. The continuation handoff staged never-delivered held text into the
   transport-continuation buffer; a continuation that diverged from
   completing the placeholder persisted the invisible prefix into
   history/JSONL while the UI showed only the remainder.

3. The staged handoff survived the turn through the MAX_TOKENS recovery
   wrapper (which rethrows non-InvalidStreamError without the folding
   catch) and could be folded into a later turn's continuation buffer.

Changes:
- Unify the deferred chunk and held placeholder-prefix chunks into one
  arrival-ordered pendingPreValidationChunks list; every flush (in-loop
  divergence release, stream-error delivery, post-loop flush) yields it in
  arrival order, so deferral can no longer reorder delivery.
- Snapshot the pending chunks with detachChunkParts before history
  consolidation merges adjacent text parts in place, so post-consolidation
  flushes deliver what actually arrived instead of rewritten text.
- On a stream error, deliver the pending chunks (mirrored by the send loop
  into the continuation buffer, keeping delivery and persistence aligned)
  unless they complete the placeholder — a completed placeholder is still
  dropped silently for a clean fresh replay. This replaces the
  pendingHeldPlaceholderText field, its staging, the send-loop fold and the
  entry reset: never-delivered text no longer enters the continuation
  buffer, which also closes the cross-turn leak.

Tests: arrival-order delivery of a deferred chunk + later held chunks;
in-order delivery of a diverged placeholder prefix without retry; a lone
placeholder-prefix chunk delivered and persisted without rejection;
delivery/persistence alignment when a held-prefix continuation diverges.
Existing split-placeholder continuation coverage still green (340/340).

* fix(core): propagate image eviction to durable history, harden placeholder gates

Round-10 review findings:

- replaceImagePayloadsInPlace now rewrites the shared Part object itself
  (part.text = reference; part.inlineData = undefined) instead of only
  replacing the content.parts[i] slot: when placeholder curation makes two
  user turns adjacent, appendCuratedContent merges them into a fresh
  Content whose parts array reuses the durable history's Part objects, and
  a slot-only swap left the inline base64 payload alive in history — the
  eviction pass re-ran every request (countAllInlineImages stayed at the
  threshold) and store.put() re-hashed the multi-MB payload each time, so
  the memory this mechanism exists to shed never left history. Nested
  functionResponse parts get the same treatment (getFunctionResponseParts
  returns the live array). Regression test pins the durable side.
- Extract nonThoughtText(parts) and use it at both placeholder gates (the
  per-chunk hold and the stream-error completed-placeholder drop) so the
  two decisions cannot drift when the text-accumulation semantics change.
- Log the one deliberate discard: the completed-placeholder drop on the
  stream-error path now emits a debugLogger.warn naming the withheld chunk
  count, so a benign transport-cut recovery is distinguishable from lost
  output after the fact.
- Comment at the eviction call site names skipParts as the load-bearing
  protection for the current message's images (the identity skipEntry find
  misses whenever curation merged the user turns into a fresh object).

* fix(core): preserve deferred stream metadata

* fix(core): close round-12 findings on degraded-placeholder defense

- XML tool-call recovery now aligns the withheld pre-validation chunks
  with the rewritten turn: their plain text is rewritten to the stripped
  remainder so consumers no longer receive the raw `<invoke>` markup
  while history persists the recovered shape (fixes the deterministic
  'retains a short text prefix' failure red-lining CI)
- completed-placeholder detection is now cross-attempt: a placeholder
  that completes across a transport cut (prefix before the cut,
  remainder before the error) is discarded instead of persisting via
  the continuation-prefix fold, and the send loop refuses to continue
  from a delivered text that equals the completed placeholder — it
  routes through the invalid-stream budget and retries fresh
- mid-stream flush of held chunks yields detached copies so post-stream
  consolidation cannot rewrite already-delivered part objects
- image reattach now resolves `Image #<id>` references from the store
  even below the eviction threshold, so fallback/recovery rebuilds
  after an in-place eviction pass don't carry markers without pixels

* fix(core): close round-13 findings on degraded-placeholder defense

R13-1/R13-4: the completed-placeholder conversion no longer throws
from inside the send loop's catch (which escaped the loop and skipped
the invalid-stream budget — zero retries, no telemetry, no fallback
chain). Both conversion sites now fall through to the budget handler:
the send-loop guard converts when the folded buffer completes the
placeholder, and the stream-error discard converts before rethrowing,
so a placeholder completed across a transport cut retries fresh with
the continuation reset instead of continuing from the garbage prefix.

R13-2: the fork boundary (copyHistoryContainers) now shallow-clones
Part objects — including nested functionResponse.parts — so a forked
chat's in-place image eviction can no longer strip payloads out of the
MAIN conversation's durable history (the snapshot chain shares part
objects by reference; the ids would exist only in the discarded fork's
store). The stale contract test is updated to pin the new isolation.

R13-3: the cross-attempt discard merges the UNTRIMMED attempt text
(trim only after the merge), matching the persistence merge so a
remainder opening with whitespace cannot slip past the discard.

R13-8: the below-threshold reference scan is skipped entirely when the
image payload store is empty (the common steady state).

R13-10: reattach candidates exclude ids already carried inline in the
same request (content-hash ids would otherwise ship the same payload
twice per send).

R13-15: the per-chunk placeholder hold compares the MERGED delivered
text when a continuation prefix is in flight, so a remainder that
finishes the placeholder over several chunks is withheld instead of
yielded inline before the post-stream gate throws.

Tests: double-cut regressions for both remainder shapes (with/without
finish frame), e2e below-threshold reattach, fork-boundary isolation
(top-level + nested), inline-id dedupe, split-tail converted to the
retry path, plus the R13-7/9/11/12/13/14 assertion strengthenings.

* test(core): pin full history in the second tool-result continuation test

R13-14 follow-up: the 'deliver every deferred chunk in a tool result
continuation' test also pinned history only via at(-1); replace with the
full expected history so a drifted partial-turn pop orphaning the
functionResponse turn cannot hide.

* fix(core): read the converted placeholder error type in budget telemetry

Round-13 follow-up: the invalid-stream budget handler's debug log and
ContentRetryEvent still read (error as InvalidStreamError).type, but on
the converted-placeholder path `error` is the raw transport error and
the InvalidStreamError lives in budgetError — the retry log printed
[undefined] and the telemetry event carried a wrong type. Read
budgetError.type at both sites.

* fix(core): exempt last-referenced images from reattach cap + harden tests

Round-14 review of the degraded-placeholder defense (QwenLM#8938).

Critical fix (image-payload-references): buildReattachParts ranked ids
referenced in the user's LAST message by their original eviction-marker
position, so under the maxRecentImages cap an explicitly-referenced
older image lost its slot to a stale, never-requested marker — the
reference-resolution feature silently failed in its primary scenario.
Ids referenced in the last content are now collected separately and
reattached unconditionally, outside the recency cap, mirroring
prepareImagePayloadsForRequest's unconditional referencedIds reattach.

Doc (forkedAgent): the CacheSafeParams.history contract now states the
shallow-clone behavior — consumers may rewrite part objects in place,
but deeper payload objects (inlineData, functionResponse.response)
remain shared and must not be mutated.

Test-efficacy hardening (all mutation- or trace-verified gaps from the
review): nested functionResponse inline-twin dedupe; cap-exempt
referenced-image regression; marker-present + placeholder-absent pins
on the eviction test; below-threshold e2e cap-binding, all-images
eviction, post-send-2 marker state, exactly-once reattach; mid-history
and consecutive-run placeholder curation semantics; shape-A double-cut
record/history agreement + corrected mechanism comment; diverged
continuation second-half delivery; plain-RETRY-after-rejection pin;
UPSTREAM_DEGRADED_RESPONSE routing pin on cut-before-finish;
deferred-chunk ORDER pin (finish chunk must be last); retry-exhaustion
no-CHUNK-to-consumer pin (via an expectStreamExhaustion collector);
and a hold-liveness test pinning that non-placeholder text is released
inline before stream end.

* refactor(core): dedupe placeholder-error and reattach-append sites

Ponytail review follow-ups on the degraded-placeholder defense (QwenLM#8938):

- Extract a degradedPlaceholderError() factory: four call sites
  (send-loop catch-convert, stream-error completed-placeholder discard,
  and the two post-stream gates) constructed the identical
  InvalidStreamError literal; the factory keeps the message/type strings
  from drifting between sites.
- Extract appendReattachParts(): both request-history branches ended in
  the same 7-line "append onto last user turn, else push a fresh one"
  block; the shared helper keeps the append shape from drifting.

* fix(core): gate placeholder conversion on !streamYieldedFunctionCall (QwenLM#8938)

- The send-loop placeholder->InvalidStreamError conversion was the only
  placeholder-retry producer that could fire after a functionCall chunk
  had been delivered: a folded '(request timeout)' completed on the same
  attempt that emitted a tool call would schedule a fresh retry after
  the consumer received the call, orphaning the tool_use/tool_result
  pairing. Add the same point-of-no-return guard every sibling path
  enforces; the original transport error now propagates instead.
- Regression test: folded placeholder + delivered functionCall => no
  third attempt, stream terminates, last chunk is the tool call.
- Test pins from review: budget-routing assertions (mockLogContentRetry
  with UPSTREAM_DEGRADED_RESPONSE) on both double-cut tests, a
  non-vacuous retry guard on the prefix+remainder rejection test, and
  the throwing structuredClone spy on the getHistoryLength no-clone
  contract (matching the sibling walk-only accessors).

* test(core): attach the rejection handler before timer flush (QwenLM#8938)

The folded-placeholder/functionCall regression test rejects the consumer
stream mid timer-advance; attaching the expect().rejects handler only
after advancing left a handlerless rejection at the timer checkpoint,
which vitest's unhandled-error gate turns into a red run under parallel
CI load (all 20094 core tests passed; exit 1 came from the single
unhandled rejection). Attach the expectation before advancing timers so
the handler is on the promise regardless of when the send loop rejects.

* test(core): satisfy vitest/valid-expect on the pre-timer rejection handler (QwenLM#8938)

The previous fix assigned expect().rejects to a variable, which
eslint's vitest/valid-expect rule rejects (async assertions must be
awaited or returned) — CI lint failed before the suite even ran. Keep
the unhandled-rejection protection by registering a plain catch handler
before advancing timers, and leave the awaited expect().rejects
assertion as the lint-compliant pin.

* fix(core): scope placeholder run invalidation to the degraded turns (QwenLM#8938)

R16-1: extractCuratedHistory dropped the WHOLE consecutive model run
when any turn was a degraded placeholder, so a sibling functionCall
turn was dropped while the following user(functionResponse) survived —
the outgoing request carried an orphaned functionResponse, exactly the
invalid pairing the continuation gate and the MAX_TOKENS hasFunctionCall
check exist to avoid (pre-PR the run stayed whole and provider-valid).

- curation now drops only the placeholder turns of a run and keeps
  valid siblings (invalid-content runs keep the pre-existing
  whole-run semantics)
- repairOrphanedToolUseTurns scan no longer lets placeholder turns
  split the model<->user pairing adjacency: an fr right after a
  placeholder sibling counts as adjacent (the mirror run
  [functionCall, placeholder] + fr previously synthesized a duplicate
  error-fr because the placeholder broke adjacency)
- the whole-run drop test is rewritten to the revised per-turn
  semantics; new regression tests pin both run directions through the
  public API (mutation-verified: the old whole-run drop fails them).
350/350 green, tsc + eslint clean.

* refactor(core): narrow degraded placeholder defense

* fix(core): preserve placeholder-aware tool adjacency

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
* fix(core): isolate image payload eviction state

* fix(core): clone history parts for background agents before in-place image eviction

* fix(core): protect image history snapshots

* fix(core): bound referenced image reattachment

* fix(core): anchor image-marker matching to the full eviction marker
…n arguments match (QwenLM#9436)

* fix(core): treat duplicate provider tool-call ids as replays only on matching args

The duplicate provider tool-call guard (QwenLM#5038/QwenLM#5657) keyed on the id
alone, so models whose ids are only unique within a single response —
e.g. Kimi emits {name}_{index} and the index can restart at 0 on any
round — had fresh calls misclassified as replays: the second collision
got a synthetic duplicate error and the third tripped the circuit
breaker, killing every turn by round three.

A handled id now maps to a (name, canonical args) fingerprint — the
same sha256 repeat key the loop guards use, moved to a leaf module so
toolCallIdUtils can share it without an import cycle. An incoming call
is a replay only when its fingerprint matches the call that first
executed under that provider id; id collisions with different args
execute normally under the unique suffixed id that normalization
already assigns. Exact same-args replays keep the unchanged QwenLM#5014
suppression and QwenLM#5657 breaker behavior at all four entry points
(AgentCore, TUI stream, non-interactive CLI, ACP daemon session).

The synthetic duplicate message now tells the model to re-issue with a
fresh tool-call id when a new invocation was intended, giving
id-emitting models a recovery path.

* qwen: address PR review feedback (QwenLM#9436)

- Fingerprint each incoming call once per carrier object: the WeakMap
  cache now keys on any stable carrier (FunctionCall part or
  ToolCallRequestInfo), and the replay predicate / recording helpers
  take the precomputed fingerprint instead of rehashing (name, args)
  on every breaker scan, admission pass, and record.
- Move the getToolCallRepeatKey tests next to the extracted leaf
  module instead of exercising it through the loop detection service's
  compatibility re-export.
- Restore the assertion pinning that runToolCalls never mutates the
  history accessor's returned fingerprint map, now that the defensive
  copy is load-bearing.

* qwen: address PR review feedback (QwenLM#9436)

Criticals:
- Canonicalize onto a null-prototype object so a literal __proto__ own
  key (preserved by JSON.parse) stays a data property instead of
  vanishing through the inherited setter — two calls differing only in
  __proto__ no longer collide on one repeat key, which the replay
  oracle would have turned into a wrongly suppressed execution.
- Clone request args at scheduler intake: callers pass args that can
  alias the model-emitted functionCall part stored in chat history, and
  the executor rewrites PATH_ARG_KEYS on request.args in place (a
  persistence the post-'ask' bounce re-execution relies on). Without
  the clone those rewrites leak into history and skew the replay
  fingerprints derived from it, letting genuine replays of
  path-carrying calls re-execute in multi-round agent runtimes.

Suggestions:
- Complete the duplicate message with the different-arguments recovery
  path required by design decision D3, for models whose provider
  assigns ids.
- Fix the copy-convention comments (the accessor returns a fresh map
  per call; copies are future-proofing) and align all four entry
  points on copying the accessor result.
- Pin the untested branches: history first-occurrence-wins for reused
  ids and orphan response-id exclusion, the fingerprint cache-hit path,
  the __proto__ distinction, the caller-args no-mutation invariant, and
  a cross-round runtime test that a replay of the original call stays
  suppressed after an id-colliding execution.
…arent (QwenLM#9391)

* feat(telemetry): link daemon HTTP request spans to inbound W3C traceparent

The daemon HTTP surface records a request span per request, but every span
starts a new trace: a caller forwarding the standard W3C traceparent header
(OTel-instrumented clients, proxies, gateways) gets no linkage back to its
own trace.

Extract traceparent/tracestate from inbound request headers in the daemon
telemetry middleware and parent the request span to that remote context.
Extraction reuses the same path as the existing JSON-RPC _meta extraction
(global propagator first, strict manual fallback so behavior is identical
without a registered SDK) and fails closed: requests without a valid header
keep the exact current span shape.

* fix(telemetry): guard inbound traceparent sampling and align W3C fallback

- Force TraceFlags.SAMPLED on inbound HTTP parents via the existing
  shouldForceSampled() matrix: an unsampled remote parent under the
  default parentbased_always_on sampler silently dropped the request
  span, the whole next() subtree, and the session-subprocess spans
  forwarded via _meta (review C1).
- Replace the hand-rolled manual fallback parser with a direct
  W3CTraceContextPropagator instance so acceptance rules (future
  versions, tracestate, all-zero ids, version-00 extension field)
  match the registered path with or without an initialized SDK.
- Gate middleware extraction behind isTelemetrySdkInitialized() to
  skip the hot-path parse when telemetry is off, and emit a debug
  daemon log when a present-but-invalid traceparent header is
  rejected.
- Re-export DaemonRequestSpanOptions from the core barrel and add a
  type-level guard so the parentContext field cannot silently
  disappear (vitest alone cannot catch its removal).

* chore(vscode): regenerate companion NOTICES.txt for @opentelemetry/core

* fix(telemetry): lazy-load OTel core fallback propagator behind SDK init

Address review feedback on the inbound traceparent linkage:

- Keep @opentelemetry/core out of the static graph. The module-level
  W3CTraceContextPropagator in daemon-tracing.ts pulled the CJS barrel
  (bot-measured +65,046 bytes) into every closure loading that module,
  including telemetry-off deployments. daemon-tracing.ts now keeps only a
  holder + setter (setDaemonFallbackPropagator, typed against
  @opentelemetry/api — type imports stay free at runtime); the lazy
  sdk-impl.ts chunk, whose closure already contains @opentelemetry/core via
  sdk-node/resources, constructs and injects the W3C instance on the
  successful SDK assembly path. Until injection, extraction returns no
  parent context: the HTTP edge is already gated on
  isTelemetrySdkInitialized (nothing changes when telemetry is off), and
  the _meta edge's consumers (withDaemonSpan / withInteractionSpan)
  short-circuit on the same flag, so an unresolved pre-init parent never
  had an observable effect.
- Add the mutation-verified fail-closed test for the header-extraction
  try/catch in daemonTelemetryMiddleware: a throwing extractor leaves the
  request settling normally (recordDaemonHttpRequest still fires once)
  with no parentContext on the span options.
- Record the rejected traceparent value (truncated to 128 chars) as
  http.request.header.traceparent on the invalid-header breadcrumb —
  traceparent only carries trace-id/span-id/flags, so this is
  privacy-safe and makes broken cross-service joins diagnosable.

Also document why the _meta extraction path deliberately skips
shouldForceSampled (trusted in-process bridge vs external HTTP input).

* feat(telemetry): carry inbound trace id into daemon access log with telemetry off

Telemetry off (the default) left daemon logs without any trace id: with no
request span, the log trace prefix never fires, so a caller forwarding W3C
traceparent could not be joined to its daemon log lines.

The middleware now parses the header with a plain regex
(extractInboundTraceId — same shape/all-zero/ff rejections as the W3C
propagator, no OTel machinery) and stores the trace id on the per-response
telemetry context. The access log emits it as the camelCase traceId field
of "request completed", keeping the log-based join alive with no telemetry
config and no trace backend. With telemetry on nothing changes: the request
span already carries the caller's trace id into the log prefix.

* fix(telemetry): unify _meta/HTTP sampling and repair build export

- Export extractInboundTraceId from the core barrel: the previous commit
  exported it from daemon-tracing.ts only, so downstream package builds
  failed with TS2305.
- extractDaemonTraceContext now applies the same shouldForceSampled()
  matrix as the HTTP edge: the _meta path is also reachable from direct
  ACP clients (acpAgent newSession/loadSession/unstable_resumeSession
  and Session.prompt pass caller-controlled _meta), so an external
  sampled=0 parent no longer silences daemon spans there either. The
  in-process bridge is unaffected (its injected values are already
  SAMPLED).
- The rejected-header breadcrumb now goes through sanitizeLogText so a
  crafted traceparent cannot forge log line structure with control
  characters.
- Add the sdk-impl wiring test: after initializeTelemetry the injected
  W3C fallback propagator resolves inbound HTTP parents.

* fix(telemetry): align log-path traceparent parsing and emit traceId in both modes

- extractInboundTraceId now mirrors the vendored W3C propagator's
  acceptance exactly: single optional leading/trailing whitespace and
  trailing extension fields above version 00 (version 00 must stay
  four fields). Previously the strict four-field anchor made the two
  paths disagree on the same forward-compatible header, silently
  dropping the access-log traceId for exactly the callers the
  propagator path supports.
- The camelCase traceId access-log field is now captured whenever a
  valid header parses, regardless of telemetry mode, so one saved log
  query / alert shape works for every deployment; with telemetry on the
  snake_case span prefix carries the same id redundantly.

* fix(telemetry): move inbound trace id getter out of the middleware module

52d572c made the access log statically import the telemetry
middleware module to read the captured inbound trace id. The access log
sits inside the serve fast-path pre-listen closure (run-qwen-serve
imports it directly), so the middleware's core-barrel import graph came
along for the ride and check-serve-fast-path-bundle started failing:
the 5.6MB core chunk (shell tool, glob, chokidar, @iarna/toml, fzf)
became statically reachable from run-qwen-serve.

Move the response-context symbol, its type, and the
getDaemonTelemetryInboundTraceId getter into a new import-light
telemetry-context.ts; the middleware imports the symbol from there and
re-exports the getter, so the access log no longer links against the
telemetry module at all.

* fix(telemetry): capture inbound trace id pre-auth under a dedicated symbol

* test(telemetry): pin the trace id seam through the context module getter

---------

Co-authored-by: 秦奇 <gary.gq@alibaba-inc.com>
* test(web-shell): add transcript contract prevalidation

Freeze reproducible evidence for current transcript paths before any
VS Code or HTML export production migration.

- Add versioned fixtures, closed export schema, and capability gates
- Probe direct-daemon and ACP identity under partial history prepend
- Preserve raw adapter semantics and full write_file Turn Output diffs
- Document the two-MR architecture, security constraints, and blockers

* fix(web-shell): harden transcript prevalidation gates

Make the evidence-only contract suite enforce the review assumptions it
documents while preserving the existing runtime transcript behavior.

- Run the contract suite in the required no-AK integration job
- Fail closed on ambiguous identity probes and deduplicate gate kinds
- Enforce manifest, hash, export safety, and renderer version boundaries
- Cover visible transcript text and stable Desktop packaging semantics
- Record the complete PR comment evaluation and verification outcome

* fix(web-shell): close transcript prevalidation gaps

* fix(web-shell): remove brittle Desktop wiring probe

Keep transcript contract prevalidation at the evidence level it can
actually prove. The previous source-text assertion could both reject
equivalent formatting and pass unreachable packaging code.

- Remove the Desktop script parser and its false behavioral claim
- Mark installed-artifact verification as deferred to Desktop smoke tests
- Clarify MR1 matrix, CI wiring, and provenance evidence boundaries
- Refresh the hash-locked capability matrix fixture

Note: This does not change Web Shell or Desktop production behavior.

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
…wenLM#9468)

* test(triage): pin the parent-computed diff and the child copy

Update the record-step pins to the RUNNER_TEMP-staged file and add a
pin asserting the scrubbed child copies it rather than re-running git.

* fix(triage): compute the flake-gate diff before the env -i re-exec

The scrubbed (env -i) child cannot read the shallow merge-ref objects:
git global safe.directory lives under HOME, which env -i strips, so git
refuses to read the base commit and the diff fails with
"Could not access <base-oid>". Compute the NUL-delimited diff in the
parent (normal environment, no PR code executes) and stage it under
RUNNER_TEMP; the clean child copies it into the root-only gate home.

* fix(triage): rm -f before the parent diff redirect, slash-path the parent commands

* test(triage): pin diff-before-re-exec ordering and slash-pathed commands

* fix(triage): harden the flake-record staging path against directory and symlink plants
…dechannel (QwenLM#9202)

* fix(sdk): route unrecognized diagnostics onto a bounded transcript sidechannel

Normalizer-classified unrecognized_event / unrecognized_session_update debug events no longer enter transcript blocks[]: they are mirrored onto a capped unrecognizedDiagnostics sidechannel instead. This stops them from finalizing a streaming assistant/thought block (which dropped a following assistant.usage frame) and from consuming the maxBlocks budget (which let repeated noise evict real conversation content). malformed_payload diagnostics and client-dispatched debug events keep their existing block semantics.

* fix(sdk): align browser bundle budget

* fix(sdk): close the sidechannel review round (QwenLM#8823)

- export the sidechannel API through the daemon barrel
  (selectUnrecognizedDiagnostics, UNRECOGNIZED_DIAGNOSTICS_LIMIT,
  DAEMON_UI_UNRECOGNIZED_DIAGNOSTIC_REASONS + types) and pin the
  reachability in daemon-public-surface.test.ts
- restore the MAX_TEXT_BLOCK_LENGTH cap on sidechannel text, mirroring
  truncateText exactly (suffix fits within the cap)
- ship the unrecognized reason subset as a runtime const array and route
  by membership, so a new reason cannot fall through to appendStatusBlock
- copy the correlation fields createBase stamps (promptId, sourceRecordIds,
  branchRecordId, originatorClientId) onto sidechannel entries; drop the
  dead source/data switches
- un-fuse the budget-history comment chain in scripts/build.js
- update docs/developers/daemon-ui for the split routing
- tests: full entry shape, text cap, block-path debugReason counterpart,
  and a webui malformed_payload interleave sibling so the QwenLM#7012
  flush-before-guard keeps a discriminating stimulus

* fix(sdk): address round-2 sidechannel review for QwenLM#8823

- build.js: bump daemon browser bundle budget 191KB -> 192KB
  (195,591 bytes measured > 195,584 cap; build failed at head)
- webui: narrow the observer-mode debug guard so unrecognized_*
  diagnostics reach the reducer sidechannel; only block-path debug
  events are dropped
- webui: merge history-store unrecognizedDiagnostics in
  applyTranscriptHistory so paged-back sessions keep diagnostics
- transcript: extract truncateTextAtLimit shared by the block and
  sidechannel truncation paths
- transcript: reset unrecognizedDiagnostics on rewind alongside the
  sibling per-turn state resets
- types: rename DaemonUnrecognizedDiagnostic.receivedAt to
  clientReceivedAt (matches the sibling block projection)
- tests: reason-prefix conformance pin, rewind reset, narrowed guard,
  history pagination merge

* fix(webui): avoid flushing sidechannel diagnostics

* fix(sdk): preserve diagnostics across rewind

* fix(webui): dedupe sidechannel history records

* fix(webui): align the paging sidechannel test with the normalizer keys

The paging test added in e6b40e5 failed deterministically (webui
suite red, CI Test job red) for two reasons:

1. The fixtures stamped only _meta['qwen.session.recordId'], but the
   SDK normalizer's extractSourceRecordIds reads
   _meta.qwenTranscript.sourceRecordIds — no sidechannel entry ever
   carried sourceRecordIds, so the dedupe assertion could not pass and
   the new displayedRecordIds loop was never exercised by a passing
   test. Stamp BOTH keys, matching production replay frames
   (acp-bridge buildUpdateMeta) and the sibling dedupe test.
2. Cap arithmetic: LIMIT-1 live entries + 2 fresh history entries =
   LIMIT+1, so the newest-wins slice evicted record-old-1 which the
   test asserted present. Emit LIMIT-2 live events so the post-merge
   total lands exactly on the cap.

Also correct the post-merge index assertions: history entries come
first (old-1, old-2), then the deduped-once live overlap, then the
first live mystery event. Suite 506/506, eslint + prettier clean.

* fix(sdk): raise diagnostic sidechannel bundle budget

* fix(sdk): raise the daemon browser bundle budget to 198KB and pin the diagnostics selector

- The sidechannel routing + selector cost ~1037 B over the 197KB cap
  (bundle measured 201893 B), failing the browser-bundle size gate; bump
  MAX_DAEMON_BROWSER_BUNDLE_BYTES to 198 * 1024.
- Fold the rebase-residue 190→191→192 KB ledger entries into the accurate
  190→195→196→197→198 lineage so the next bump has one canonical history.
- Add a behavioral pin for selectUnrecognizedDiagnostics: it must return
  the routed sidechannel itself (toBe), discriminating a `return []` or
  shallow-copy regression that the typeof-only surface test cannot see;
  flip-verified.

* fix(sdk): reset the user pointer on sidechanneled diagnostics, share the routing predicate

appendUnrecognizedDiagnostic left activeUserBlockId untouched while the
replaced appendStatusBlock path reset it for every non-user block; a
later mergeable user.text.delta with no promptId stamp (e.g. a peer
client's $ <cmd> echo) then appended onto the earlier user block
across the diagnostic, collapsing two user turns into one and skewing
rewindTranscriptToUserTurn's kind==='user' turn indexing. Keep the
reset (assistant/thought pointers stay untouched, the point of the
sidechannel); witness test flip-verified red without the one-line reset.

Also export isUnrecognizedDiagnosticReason from types.ts next to
DAEMON_UI_UNRECOGNIZED_DIAGNOSTIC_REASONS and call it at all three
routing-guard sites (reducer, provider flush condition, provider drop
filter) so the QwenLM#7012/QwenLM#8823 guard pair classifies every debug event
against one source instead of three hand-written copies.

* fix(ci): prevent bite harness SIGPIPE

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
)

The Actions backend wedged the original qwen-autofix.yml workflow entity
on 2026-08-19: runs stick "queued" with zero jobs and cannot be cancelled
or deleted via API, schedule ticks stopped being created, and event-
triggered runs are dropped. Same-repo control workflows run normally, so
the failure is bound to that one workflow entity. A byte-identical copy
under a new path registers as a fresh entity and resumes the loop; the
original file stays untouched so the revert is deleting this one file.
…kill (QwenLM#9345)

* refactor(cli): consolidate shared helpers ahead of the legacy audit skill

Move the pieces the upcoming /audit skill needs out of command-group
ownership so no skill imports across command groups:

- the findings schema moves from commands/review/ to cli/src/utils/ as-is;
  every review consumer imports it from the new home, and the stale-bundle
  digest, bundle-asset list, and artifact comment track the move
- safeTarget (traversal-safe slug) and tokenizeArgs (quoted argument
  splitting) lift to cli/src/utils/paths.ts and shell-args.ts, with
  review's copies re-exporting/redirecting
- the two private git check-ignore copies (review test-plan, team memory)
  consolidate into one fresh-by-default helper in core utils; the memo
  stays caller-side so a remedy re-check observes the flip

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

* fix(cli): restore review ledger machinery dropped by the consolidation

The shared-helper consolidation silently reverted three behaviors PR QwenLM#9175
added to compose-review, against the PR's stated "no behavior change"
intent: the unreviewed-dimension anchor exemption (scopeUnproven /
dimensionGapsAreDepthOnly / isNonDiffDimensionGap), the LEDGER_MAX_ROUND
stamp clamp, and the bilingual budget-stop phrase splice. Restore them with
the tests that pin them; SKILL.md, ledger.ts, and deadline.ts still
document all three.

Also harden the new helper tests:
- safeTarget: the deep-path fixtures now share a flattened prefix longer
  than the kept window, so a truncation-only slug (no digest) collides
  instead of shipping green.
- isGitIgnored: each GIT_* scrub arm now carries a discriminating fixture
  (three arms previously passed with their scrub line deleted), and every
  foreign git init scrubs ambient repo-placement selectors.

* fix(core): scrub git config-injection channels from the ignore probe

Ambient GIT_CONFIG_COUNT (inline KEY/VALUE injection) and
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM (config-file redirects) can aim
core.excludesFile at a foreign rules file and flip the probe's verdict
for the -C worktree — the same leak class the existing selector scrubs
close. Measured on the pristine probe: both channels turn a
not-ignored path into an ignored one.

Also pin the guards the review found unpinned: the two config
channels, the `--` separator for dash-leading paths, the timeoutMs
wiring, safeTarget's hash-of-original-target property, and
tree-existence of the lifted review helpers.

* fix(core): scrub pathspec-magic env channels from the ignore probe

* fix(core): drop the whole GIT_* env family from the ignore probe

Two more leak channels surfaced on the probe's per-variable scrub list:
GIT_ICASE_PATHSPECS (the fourth pathspec modifier — ambient, makes
check-ignore reject every pathspec with exit 128, which the catch reads
as not-ignored) and GIT_CONFIG_PARAMETERS (the inline -c channel git
itself uses to propagate config to children — ambient, can aim
core.excludesFile at a foreign rules file). Measured through the real
function: the first flips a genuinely ignored path to not-ignored, the
second flips a not-ignored path to ignored.

Since the channel list grew by one leak per review round, drop the
whole GIT_* family instead of enumerating, and close the system config
tier explicitly (GIT_CONFIG_NOSYSTEM=1) so host policy in
/etc/gitconfig can no longer answer for the -C worktree — that ambient
dependency also made the config-redirect arm red on any host whose
system config matches the probe path. Pin the icase member, the
PARAMETERS channel, the default 5 s deadline (previously unpinned), and
add a lower timing bound to the caller-deadline arm so it cannot pass
vacuously when the shim is not executable.

* fix(cli): make the safeTarget slug space prefix-free at the dash boundary

Review's cleanup sweeps .qwen/tmp/ by qwen-review-<slug>- prefix. A slug
that itself carried '-' — natively (pr-6771 vs pr) or via the truncation
join — could extend a shorter slug, letting one target's cleanup delete a
DISTINCT target's artifacts (R8-7). The truncation branch this PR carries
newly lands deep targets inside the cap instead of dying ENAMETOOLONG,
which turned the latent collision live.

Drop '-' from the slug alphabet entirely (dashes flatten like separators)
and join the truncation digest with '_': with '-' out of every slug, the
qwen-review-<slug>- boundary is unambiguous by construction — no slug can
start with another slug plus '-'. prev-ledger side files keep their
hardcoded dashed name on both writer and reader, untouched by the slug.

Tests pin the prefix-free property (short-vs-short, short-vs-truncated)
and the fixture names follow the new slugs.

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

* revert(cli): restore safeTarget byte-identical to the pre-lift behavior

Reverts the slug alphabet change (5394952) and drops the deep-target
truncation branch, per review round findings R12-1 and R8-7:

- R12-1: flattening dashes renamed only one side of review's two-sided
  artifact-naming contract — bundled-skill templates, composed names,
  prev-ledger and brief/report producers hardcode the dash spelling, so
  the bypass-audit tripwire would silently skip and the cleanup sweep
  would leak ~15 dash-form artifacts per review.
- R8-7: the prefix-sweep hazard only exists because of the truncation
  branch this lift carried; main's safeTarget has no truncation, so a
  behavior-preserving lift must not add it. Deep-target support belongs
  in a follow-up paired with the sweep-side structural fix it needs.

safeTarget is now byte-identical to the pre-lift implementation (moved,
not modified); tests pin the dash spelling and the leading-strip rule as
they behave on main. Full review suite green (5022).

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
…enLM#9479)

Since ~2026-08-19 00:00 UTC the workflow's schedule, issue_comment, and pull_request triggers stopped creating runs while pull_request_review kept working; dispatch runs were created but never expanded into jobs (three sat queued with zero jobs for 4-11 hours). The on: block was unchanged throughout and a disable/enable cycle did not restore dispatch, consistent with a stale trigger registration on the Actions backend. Any content change forces a re-parse; this commit is that change (one comment line).
…reaches (QwenLM#9416)

* fix(review): keep the blocker in a COMMENT body every softening path reaches

`compose-review` renders the body copy of an unanchorable blocker on a
COMMENT only when that COMMENT stands where a REQUEST_CHANGES would have
been — the body copy is its ONLY copy, and softening the event must never
erase it. The condition listing which softenings qualify was written as an
enumeration of the two flags known at the time:

    if (downgradedFrom === 'Request changes' || criticalsUnverified)

A third path shipped past it. The findings-file `— [unverified]` tag softens
a Request changes at the event line, and it sets neither flag: not
`downgradedFrom` (only the presubmit carve-out sets that) and not
`criticalsUnverified` (only the verification-delivery gate sets that). So a
run whose coverage was PROVEN and whose verifier ran posted a
239-character body — the opener, the tag disclosure, the footer — with the
blocker nowhere in it, while the verdict line and the artifact both counted
it.

The condition is derived now rather than enumerated: `baseEvent` is the row
before every cap and downgrade, so `baseEvent === 'REQUEST_CHANGES' &&
event === 'COMMENT'` asks the question the clause is actually about, and
answers it for softening paths that do not exist yet. Same closure the
module applied to the deferral channel after QwenLM#9095.

The regression test builds the isolating shape — covered plan, verifier on
record, findings file still carrying a tag — and asserts both flags the old
condition read are unset while the blocker rides the body. Reverting to the
enumeration reddens it; so does dropping the clause.

* fix(review): keep the presubmit downgrade reasons on every softened Request changes

* fix(review): keep the presubmit downgrade reasons on every softened Approve
…LM#9476)

* feat(web-shell): Consume live-state session activity timestamps

* qwen: address PR review feedback (QwenLM#9476)
…enLM#9473)

The two-author certification bar — a CLI-built prompt delivered verbatim,
plus the agent demonstrably opening its brief, the diff, or the findings
list its prompt named — was re-implemented in four places: the coverage
walk, the layer-audit gate, retirement, and the resume recovery command.
Re-implementing a bar means drifting from it, and each copy had drifted in
its own direction.

Extract the atoms into `lib/certification.ts` — `chunkOfKey`,
`declaresOwnUncoverable`, `openedBrief`, `readBrief`, `readFindingsPointer`
— and have coverage, layer-audit-gate, retirement and `recover-findings`
compose the same functions. No behavior change: the atoms are the exact
predicates the live pipeline already ran, now spelled once so a future
edit to the bar reaches every consumer.
…LM#8855)

* feat(cli): show loaded context files alongside the first prompt

When context files (QWEN.md / context.fileName) are attached to the
system prompt, surface a one-shot INFO line above the user's first
prompt listing exactly which files were loaded, so users can verify
discovery (e.g., catch typos in context.fileName) without digging
into debug logs.

Also shorten display paths for files under the user home to `~/...`
in both the announcement and the /context detail breakdown.

Fixes QwenLM#5267

* fix(cli): address review feedback on context file visibility

- Resolve memory-marker paths against the session working directory
  instead of process.cwd() in /context detail (ACP/daemon sessions)
- Sanitize display paths with stripAnsiAndControl before they reach
  the terminal
- Drop empty context files from the announced list to match
  concatenateInstructions' empty-content filter
- Delegate home-dir shortening to tildeifyPath (with an optional home
  override for tests) instead of a second prefix-check implementation
- Align the one-shot announcement guard with downstream input
  classification (trim, /btw, shell mode)
- Add tests for the announcement latch, parseMemoryFiles ~ shortening,
  CWD-under-home and home-prefix-collision cases; use shared-volume
  fixtures so tests hold on Windows

* fix(cli): address second-round review feedback on context file visibility

- Skip the announcement latch for blank submissions (dropped by the
  message queue before reaching the model)
- Treat Windows cross-drive relative results (absolute paths) as
  outside the CWD tree so home-dir files still get `~` shortening
- Extract the hasAttachedContent predicate shared by
  concatenateInstructions and contextFilePaths so "displayed =
  attached" holds by construction
- Thread the loader's resolved userHomePath through
  formatContextFileDisplayPath into tildeifyPath so discovery and
  display agree on the home directory
- Pin ordering (announcement precedes submission admission), add a
  whitespace-only-file filter test, exercise workingDir != cwd in the
  /context detail test, and cover the /directory add reload wiring

* fix(cli): address third-round review feedback on context file visibility

- Consume the one-shot latch on model-invocable slash commands: skills and
  MCP prompts are expanded into a submit_prompt that reaches the model, so
  excluding all slash commands deferred the announcement to a later plain
  prompt (or hid it entirely in skill-only sessions).
- Build contextFilePaths from every attached file, not just memory-named
  ones, so the announcement matches what concatenateInstructions injects
  (extension context files with custom basenames were attached but
  unannounced while /context detail listed them).
- Correct the contextFilePaths JSDoc: entries are display paths (CWD-relative
  or ~/... shortcuts), not paths to resolve against the CWD.
- Tests: model-invocable skill first turn consumes the latch; shell-mode
  submissions do not; extension files with custom basenames are announced.

* fix(cli): address fourth-round review feedback on context file visibility

- Replace the order-dependent shell-mode test with a pure predicate
  (consumesContextAnnouncementLatch) and hermetic unit tests: the old
  test failed deterministically when run in isolation because the React
  harness state toggle depended on earlier tests' side effects.
- Keep latch consumption correct while shell mode is active for slash
  commands, which route before the shell intercept: model-invocable
  ones (skills, MCP prompts) still consume it.
- Consume the latch only when something was actually announced, so
  files attached later in the session still get their one-shot notice.
- Skip latch consumption for queued (deferUntilIdle) submissions, which
  are admitted only after the queue drains.
- Re-arm the latch on same-process session switches (/clear) via an
  effect keyed on the session id.
- Classify CWD containment with isSubpath instead of startsWith('..'),
  which misclassified in-tree directories named like '..cfg'.
- Document the deliberate exclusions: baseline rules are not announced
  (see ruleCount), and the Windows cross-drive arm is consciously
  untested on POSIX CI.

* fix(cli): address fifth-round review feedback on context file visibility

- Revert the deferUntilIdle exclusion (R4-8): it silently broke the
  announcement for queue-only submissions, which never pass back through
  handleFinalSubmit once the drain admits them. The latch is now consumed
  at queue time; emitting at the drain admission choke point is a deeper
  refactor deferred for this feature.
- Re-arm the latch on Ctrl-L clear-screen, which wipes the emitted INFO
  item without a session switch (completes the screen-clear case R4-13
  named).
- Soften the predicate docstring and the guard comment to be honest
  about the heuristic: btw is deliberately exempt (a side question that
  doesn't advance the main conversation, even though it may fork a model
  call), not "bypassing the model"; consumption is a prediction, not an
  admission guarantee.
- Correct the ANSI-stripping test comment to the real mechanism
  (stripVTControlCharacters matches the ESC[2Jb…BEL run as one
  BEL-terminated sequence), so a future maintainer doesn't misdiagnose.

* fix(cli): re-arm context-file announcement latch on conversation rewind

- Rewind (/rewind / double-Esc) filters history to before the target
  turn via loadHistory, wiping the emitted INFO item without a session
  switch. Add a conditional re-arm in handleRewindConfirm: the latch
  re-arms iff the rewound history no longer contains the announcement,
  so rewinding past it re-announces on the next prompt while rewinding
  to a later turn doesn't duplicate it. Completes the wipe-path coverage
  alongside /clear (session id effect) and Ctrl-L (handleClearScreen).

* fix(cli): correct rewind latch polarity and cover Ctrl-L re-arm

- R6-1 (Critical): the rewind latch re-arm shipped inverted. Latch
  semantics are true=consumed / false=armed; the correct assignment is
  truncatedUi.some(announcement) — true (stay consumed) when the INFO
  survives the rewind, false (re-arm) when it was filtered out. The
  negation inverted both branches: rewinding past the announcement left
  the latch consumed (no re-announce), rewinding to a later turn armed
  it (duplicate). Drop the negation.
- R6-2: add 're-arms the latch after Ctrl-L wipes the INFO' — submit,
  handleClearScreen, submit again, assert two announcements. Locks the
  R5-8 Ctrl-L re-arm; removing that line now fails the suite.

* fix(cli): address R8 review on context-file announcement

- AppContainer.tsx: performMemoryRefresh anchors on getWorkingDir() not
  process.cwd(), matching the /context read site (R1) and ACP/daemon under
  skipProcessChdir (R8-5).
- commandUtils.ts: docstring drops the false "MCP prompts" claim
  (McpPromptLoader has no modelInvocable field) and the inline comment
  names the disableModelInvocation / description-less exemptions instead
  of a false exclusivity (R8-4, R8-7).
- AppContainer.test.tsx: renderRewindHarness takes optional history /
  contextFilePaths and gains two rewind tests — past the announcement
  re-arms (re-announces), retaining it stays consumed (no dup); each fails
  under its !some/some mutation, covering the R6-1 inversion (R8-1).
- directoryCommand.test.tsx: strengthen the loadServerHierarchicalMemory
  assertion to pin the getWorkingDir() anchor (R8-6).

R8-2 (defensive reset) and R8-9 (/cd plumbing) declined: the reset is on
a path that can't hold stale values (safe-mode early return); /cd needs
cross-file plumbing, same class as the deferred /restore — both are
subsumed by a self-healing latch follow-up.

* fix(cli): address R9 review on context-file announcement

- commandUtils.ts: narrow the btw exemption to /btw only. ?btw is not a
  slash command and goes to the main model as a plain query, so it must
  consume the latch. Updated 2 tests that pinned the old ?btw exemption
  (R9-2).
- AppContainer.tsx: document the /cd latch gap in the consume-point
  comment — relocateWorkingDirectory swaps the file set without re-arming,
  deferred to the self-healing latch follow-up (R8-9).
- AppContainer.test.tsx: add performMemoryRefresh anchor test that mocks
  loadHierarchicalGeminiMemory, captures the callback from useGeminiStream
  mock args, and asserts the first arg is config.getWorkingDir() (not
  process.cwd()) and setContextFilePaths received the loader's paths (R9-1).

* fix(cli): address R10 review — shared constant, project-local marker test, latch-consuming rewind test, gap comment fix

- Extract CONTEXT_FILES_ANNOUNCEMENT_PREFIX constant + isContextFilesAnnouncement
  predicate in commandUtils.ts; emission site, rewind matcher, and all test
  fixtures/helpers now reference them, eliminating exact-spelling coupling
- Add project-local marker test (QWEN.md, docs/QWEN.md) with getWorkingDir !=
  process.cwd() to kill anchor-divergence mutation
- Rewind re-arm test now submits before rewinding to consume the latch,
  asserting 2 total announcements so the deletion mutation is killed
- Correct consume-point comment and add /directory add + performMemoryRefresh
  to the known-gap list alongside /cd

* fix(cli): widen isContextFilesAnnouncement param type to string for tsc --build

HistoryItem union includes variants whose type is not in the MessageType
enum (e.g. 'about', 'stats'), so { type: MessageType } is structurally
incompatible. Widen to { type: string } so the predicate accepts all
HistoryItem variants; the runtime check (=== MessageType.INFO) is unchanged.

* fix(cli): address R11 review — stale mock params, type-check test, ordering assertion, positional pinning

- Correct loadServerHierarchicalMemory mock parameter list in config.test.ts
  to match real signature (extensionContextFilePaths is slot 4, not slot 5)
- Add 3 unit tests for isContextFilesAnnouncement type discriminant
  (non-INFO item with prefix must not match)
- Replace invocationCallOrder[0] with findIndex-based assertion to isolate
  the announcement's own call index
- Pin folderTrust slot to true in directoryCommand.test.tsx
- Pin extensionContextFilePaths and contextRuleExcludes with distinct
  sentinels in performMemoryRefresh test to catch same-typed swap

* fix(cli): add /resume to known-gap list in latch comment

/resume of the current session wipes UI history (announcement INFO not
persisted) without re-arming the latch — same class as /cd, /directory
add, and performMemoryRefresh. Documented as a known gap; self-healing
latch follow-up covers all centrally.

* fix(cli): reconcile announcement latch on any history replacement

Wrap loadHistory with latch reconciliation: after rewind, /restore, or
same-id /resume, the latch is set from whether the restored history
contains a context-files announcement. Fixes the /restore duplicate
announcement and the same-id /resume missing-announcement gap; replaces
the rewind path's explicit hunk with the same shape.

Also document the intentional fileCount vs contextFilePaths criteria
difference in the loader, and fix a pre-existing test bug: the
remount-only refresh test's mock destructuring was missing the history
parameter, so it never actually captured refreshStatic.

* fix(cli): route interactive /resume through latch wrapper and sanitize markers

useResumeCommand now accepts an optional loadHistory override; AppContainer
passes the latch-reconciling wrapper so interactive /resume (including
same-id) reconciles the latch instead of leaving it consumed with no
announcement in the rebuilt history. The wrapper also destructures
loadHistory so its useCallback deps hold the stable function reference
rather than the per-mutation historyManager identity, keeping history out
of commandContext's rebuild path.

concatenateInstructions sanitizes the marker displayPath with
stripAnsiAndControl: newline/control characters in directory names could
previously forge or hide entries in the /context parser, contradicting the
sanitized announcement surface.

Documented that contextFilePaths lists top-level files only (@import
content is inlined into its importer). Added tests for the sessionId
re-arm effect, the startup-resume armed latch, and the resume override.

* test(cli): stub initialize in sessionId re-arm test to stop unhandled rejection

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
…ms (QwenLM#9445)

* feat(review): add runtime-axis, table-sweep and isolation witness forms

The witness rule's forms all answer "what does this input produce":
probe runs a unit harness, base-tree runs one input on two trees,
extract-step executes a lifted run: body, the sweep iterates a
population. Three common claims fall outside that shape.

A forward-compatibility claim is unfalsifiable on the one runtime the
harness happens to be running, and a green matrix is only evidence
about the versions in it. base-tree's version axis is git; this adds
the runtime axis, capped at one other version and priced at a tarball
rather than an install and a build.

A hardcoded table mirroring another system's namespace looks like data
rather than logic, so it gets checked by reading it against a list
somebody retypes — which is the mirrored oracle the sweep already
rejects, wearing a disguise. Parsing the literal out of the source and
diffing it against the authority at runtime makes both directions
visible, and the direction that ships is the one no test written
against the table can see.

A claim about an aggregate invites a per-component dump, after which
the verdict rests on code the review itself wrote. Removing one
contributor and re-reading leaves both numbers coming out of unmodified
code, and settles sum-versus-maximum in a single pair of readings.

None of the three needs a new CLI surface: the witness field already
carries free text, so the witness rule's enumeration of forms is the
only other line that had to move.

* fix(review): teach the three new witness forms to the verify brief

Round 1 review, R1-1: the Step 4 verifier never reads SKILL.md — its
whole instruction set is the brief agent-briefs.ts builds — and every
pre-existing form in the witness rule's enumeration has a capability
paragraph there, including the impact sweep (phrased "sweep the real
population", which is why a search for the SKILL.md name missed it).
The three new forms had none, so the only agent that produces witnesses
could not reach them. They now sit next to their kin: the table sweep
after the population sweep, the version axis after the base-tree A/B,
elimination after the drive block, and all three in the brief's own
witness-form list.

Also from round 1:

- The subtraction rule was wrong for the aggregate the worked example
  actually is. A difference is the removed contributor's value only
  under a sum; under a maximum, removing a non-holder moves nothing and
  removing the holder exposes the next-largest, so the stated rule would
  have had a verifier quote 0.2 MB as a child's heap. Both files now
  read the pair for the combining rule, and name reducing the
  population to one contributor as what yields a value outright (R1-10).
- Whether a silent contributor is skipped or counted as zero is
  invisible under a sum and a maximum alike; it shows only where a zero
  moves the figure. Scoped to counts, denominators and averages (R1-11).
- The two-child reading is quoted the same way in both files now, and
  no longer claims to prove more than it does: flat under addition
  excludes a sum, not every alternative (R1-8, R1-9).
- Node 22 exposes eight old-generation names, not ten; the ten-name set
  is the classifier's, and the two Node 24 adds are named (R1-7).
- One name for the capability, "version axis", matching its defining
  paragraph and the DESIGN.md heading (R1-3), and the witness rule no
  longer points below for a form that is above it (R1-2).
- The enumeration trap is conditional on an unbounded entrance space,
  and most of these tables are bounded — the lens's own carve-out (R1-5).
- "Installs the other version" against "no install and no build" read as
  a contradiction; the cost is a download, no dependency install (R1-6).
- The one-version cap and "every version the repo supports" contradicted
  each other. Both now say: the versions the claim names, which for a
  support range is the floor and the newest (R1-4).

---------

Co-authored-by: wenshao <nigolaschao777@gmail.com>
…QwenLM#9517)

GitHub does not start runs for a workflow file larger than 500 KB (512,000
bytes) and reports nothing when it stops. qwen-autofix.yml crossed that line
on 2026-08-19 at 512,782 bytes: schedule ticks stopped firing, every
workflow_dispatch sat "queued" forever with zero jobs and could not be
cancelled, and issues/issue_comment went quiet — while pull_request_review
runs kept succeeding, because a PR event resolves the workflow from the PR's
own branch and those carry older, smaller copies of this file. The loop
therefore looked half-alive and stayed dark for a day.

Move 75 long comment blocks (1,326 lines) verbatim into a sibling design
record, .github/workflows/qwen-autofix.md, leaving each block's opening lines
plus a `qwen-autofix.md#af-NNN` pointer where it sat: 518,055 -> 426,437
bytes. No executable line changes — the YAML parses to an identical document
outside `run:`, every `run:` script still passes `bash -n`, and the only lines
removed anywhere are comments. Steps that are duplicated verbatim across jobs
share one pointer so they stay byte-identical.

Add .github/scripts/check-workflow-size.sh (gate at 470,000 bytes), wired into
CI on every profile: a .github-only PR classifies as `github_ci_only` and
skips the `full`-only checks, which is exactly the PR that can trip this.
Tests pin the gate, every workflow's size, and pointer/section symmetry.

Delete qwen-autofix-recovery.yml. It was cloned during the incident on the
theory that the workflow ENTITY was wedged, but it carried the same oversized
file, so its dispatches queued identically and its schedule never fired.
* feat(cli): add agent view supervisor runtime

* feat(cli): add agent view pty workers

* fix(cli): harden agent view supervisor runtime

* fix(cli): harden agent view pty workers

* fix(cli): surface supervisor startup failures

* fix(cli): handle supervisor subscription failures

* fix(cli): address review feedback on agent-view supervisor (QwenLM#7799)

- Narrow AgentViewSupervisorSubscriptionOptions to omit timeoutMs,
  which the subscription never reads
- Throw when DEV=true with a .ts entrypoint but tsx is missing,
  instead of silently spawning a process that will crash
- Add test for the qwen fallback path when argv[1] is undefined
- Add test for worker sideband auth bypass (workerEvent without token)

* fix(cli): address review feedback on agent-view supervisor (QwenLM#7799)

* fix(cli): address review feedback on agent-view supervisor (QwenLM#7799)

* fix(cli): address review feedback on agent-view supervisor (QwenLM#7799)

* fix(cli): bind streaming supervisor handlers and keep attach stream 8-bit clean (QwenLM#7799)

* fix(cli): pause attach socket during leftover write and harden error handlers (QwenLM#7799)

* fix(cli): harden supervisor shutdown tests and clean up signal listeners (QwenLM#7799)

Pass the auth token to expectSupervisorUnreachable so it distinguishes a
live auth-gated supervisor from a closed socket, remove SIGTERM/SIGINT
listeners on the non-signal shutdown path, exercise prepareSocketPath's
mkdir via a nested socket path in the permission test, and add coverage
for the 1 MB request-line size guard.

* fix(cli): guard fire-and-forget promises and add supervisor timeout test (QwenLM#7799)

* fix(cli): normalize socket path test prefix and force credential file mode (QwenLM#7799)

* fix(cli): address agent view runtime review feedback

* test(cli): cover agent view bridge edge cases

* test(core): isolate worktree symlink traversal fixture

* Revert "test(core): isolate worktree symlink traversal fixture"

This reverts commit 09f6d44.

* fix(cli): harden agent view supervisor store, auth, and streaming (QwenLM#7799)

* fix(cli): harden agent view store joins, writes, and coverage (QwenLM#7799)

* fix(cli): harden agent view credential writes and subscription handshake (QwenLM#7799)

* fix(cli): harden agent view store normalization, roster safety, and bridge backpressure (QwenLM#7799)

* fix(cli): guard response serialization in supervisor server (QwenLM#7799)

* fix(cli): harden agent view pty host teardown

* fix(cli): fail fast on pty host auth rejection

* test(cli): cover pty host remote exit polling

* fix(cli): address agent view pty worker review nits

* fix(cli): harden pty host socket fallback

* fix(cli): harden agent view pty workers

* fix(cli): harden agent view pty workers

* test(cli): cover pty host spawn contract

* fix(cli): guard agent view pty host socket takeover and races

* fix(cli): fit agent view pty logs under wire cap, strip host token after merge

* fix(cli): harden pty host fallback selection

* fix(cli): address agent view worker review blockers

* fix(cli): bound attach lease ttl and surface pty host input/listen failures

* fix(cli): serialize pty host socket ownership and keep attach transport byte-transparent

* fix(cli): escalate pty host shutdown and harden socket path fallback

* fix(cli): harden win32 kill, decoder reset, and exit tracker

- kill/shutdown pass no signal on Windows (WindowsTerminal.kill throws
  for any signal string; argument-less kill terminates conpty tree)
- input StringDecoder is reset at attach boundaries so incomplete UTF-8
  bytes from a prior session cannot leak into the next
- createRemoteExitTracker requires two consecutive probe failures before
  resolving exited, preventing false death on a single transient timeout
- defaultSpawnPtyHost pairs detached with windowsHide: true
- stringifySandbox object branch test, shutdownHost else-branch test,
  win32 kill guard test, decoder reset test

* fix(cli): skip POSIX signal tests on Windows and reclaim empty lock files

R9-1: 'passes kill signals' and 'gracefully shuts down with SIGTERM' assert
POSIX signal pass-through, but the R8-36 fix made Windows pass undefined.
Skip both on win32 to prevent merge-queue CI regression.

R9-2: A zero-byte lock (process killed between writeFile open and pid write)
yielded parseInt('') = NaN, which failed Number.isInteger and never entered
the reclaim branch. Flip the condition so non-integer/empty locks are also
treated as stale.

* fix(cli): remove abort listener on delay timeout path

R10-1: delay() added an abort listener with { once: true } but only
removed it when abort fired. When the timer resolved first, the listener
stayed registered on the shared AbortSignal. Extract onAbort into a
named function and removeEventListener on the timeout path.

* fix(cli): don't optimistically resolve exited on SIGINT for remote handles

wenshao's local verification (probe P19) showed that kill('SIGINT')
on a reconnected handle (child === undefined) resolves exitTracker
immediately, even though the worker can trap SIGINT and survive.
Only resolve for terminal signals (SIGKILL, SIGTERM, default);
let createRemoteExitTracker's status poller observe whether the
worker actually exits after SIGINT.

* test(cli): add SIGINT carve-out test for remote handle kill

Exercise the 'allowedSignal !== SIGINT' guard: connect a childless
handle, kill('SIGINT'), assert exited does not settle within 100ms,
then kill('SIGTERM') and assert it resolves. Mutation-verified:
deleting the guard makes this test fail.

* fix(cli): Only resolve exited on SIGKILL for remote handle kill

SIGINT/SIGTERM can be trapped and survived; the server-side kill op has no SIGKILL escalation, so optimistically resolving exited for them could report a still-alive worker as dead. Only SIGKILL is untrappable and guarantees the worker is gone, so resolve immediately for it alone and defer other signals to the remote exit poller.

* fix(cli): harden agent view PTY host socket lock and kill default

The pid lockfile reclaim path could double-acquire: two concurrent reclaimers of a stale lock could both proceed (the later rm deleting the rival's fresh lock), and a rival could reclaim a live writer's lockfile during the O_EXCL create-then-write window — letting two hosts race through prepare→listen and orphan the displaced one. Re-read before removing a stale lock, only release a lock that still contains this pid, and re-verify ownership after listen, failing closed with EADDRINUSE when displaced so at most one host serves the socket. Also pin the signal-less kill op to SIGTERM instead of node-pty's POSIX SIGHUP fallback, which bypasses ALLOWED_KILL_SIGNALS and is commonly ignored.

* fix(cli): fix agent view PTY host dispose signal, ring trim, and lock loop

dispose() used node-pty's signal-less kill, which falls back to SIGHUP on POSIX and is ignored by nohup-style workers; align it with shutdown() by passing SIGTERM. BoundedOutputRing.trim() ran the leading-continuation-byte re-sync unconditionally, discarding leading bytes (and inflating droppedBytes) when the window never overflowed; gate it on a size trim actually running. The socket lock loop's attempt bound made a successful reclaim on the final iteration throw EADDRINUSE instead of retrying the freed lockfile; loop until the create wins or a live holder is confirmed. Also consume the required-but-unused ACTIVE_CWD sideband field as the state report's cwd fallback, which cannot throw ENOENT like process.cwd().

* fix(cli): settle remote pty host exit only after the RPC lands

* fix(cli): optional-call shutdown in the lost-RPC regression test

* fix(cli): wait for PTY host endpoint shutdown

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: yiliang114 <effortyiliang@gmail.com>
* fix(dingtalk): attach media from quoted messages

* fix(dingtalk): keep the reply text when attaching quoted media

R1-1: `attachMedia`'s placeholder cleanup was written for the direct-media
path, where `extractContent` generates `(audio)` / `(video)` /
`(file: name)` itself. This PR made the quoted-media path reach it, and there
`envelope.text` is the user's own reply — so a reply reading exactly like one
of those placeholders was blanked and the agent got an attachment with no
prompt. A group `@Bot (audio)` arrives here as exactly `(audio)`, the mention
having been stripped upstream. `attachMedia` now takes the placeholder to
erase as a parameter; only the direct-media call site passes one.

R1-2: the same path newly routes text-only replies through the unguarded
`mkdirSync`/`writeFileSync`/`basename` block. Those are synchronous throw
sites — ENOSPC on a write of up to 50 MB, ENAMETOOLONG from a quoted fileName
over 255 bytes (`basename` does not truncate), a TypeError from a truthy
non-string fileName. An escape rejects `processMessage`, whose catch sends
the generic error reply and never calls `handleInbound`; the msgId is already
in `seenMessages`, so DingTalk's retry is deduped and the prompt is lost for
good. The block now degrades the way a failed download already does: log,
skip the attachment, deliver the text. This also covers the pre-existing
direct-media path.

Verified: dingtalk 310/310. Both mutation-checked — restoring the caller-blind
cleanup fails 3 tests, letting the fs block throw fails 1.

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

* fix(dingtalk): address quoted-media review findings

Unify the msgType->mediaType mapping in a shared helper, make extractContent
the single source of truth for the placeholder text cleaned on attach, and
remove the store directory when a media write fails so failed stores no longer
leak into tmpdir. Merge the stacked attachMedia JSDoc blocks, document quoted
media downloads, and pin the previously uncovered paths: unmapped quoted
msgTypes with a downloadCode, own-media + quoted-media combinations, direct
placeholder cleaning, and the degraded-store attachment shapes.

* fix(dingtalk): file-back a quoted image colliding with the own image

* fix(dingtalk): give generated media store names a mime-derived extension (QwenLM#9347)

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* feat(core): expose workflow execution state

* fix(cli): initialize workflow snapshot state

* fix(core): preserve workflow client gating

* fix(core): snapshot workflows before journal drain

* fix(core): sanitize workflow replay logs

* fix(core): preserve workflow failure trace integrity

* fix(core): validate workflow snapshot run IDs

* test(core): cover workflow event boundaries

* fix(core): isolate workflow run callbacks

* fix(core): align workflow log projections and dependency tails (QwenLM#9034)

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

* fix(core): keep persisted workflow projections in agreement (QwenLM#9034)

---------

Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…le (QwenLM#9484)

* refactor(review): route the certification path atoms through one needle

`openedBrief`, `readBrief` and `readFindingsPointer` each inlined the same
two lines — `JSON.stringify(path)` then `args.some(a => a.includes(...))` —
which is the single mechanism enforcing the bar's "exact path, not a
`${path}.bak` look-alike" guarantee. Three copies is the exact drift class
this module was created to remove, turned on the module itself: a fix to
the match (a serialization edge, path normalization, a stricter compare)
would have to land in three places in lockstep or leave one atom silently
weaker than its siblings.

Extract a private `namesPath(args, path)` and have all three route through
it. No behavior change — the existing `.bak`-trap pins in
`certification.test.ts` and `check-coverage.test.ts` cover each atom and
pass unchanged.

* refactor(review): finish the needle consolidation, and keep its name its own

Two findings on the first round, both about the helper this PR introduced.

The consolidation stopped one copy short. `parseTranscript` still inlined the
same exact-path predicate for the diff-read half of the bar, so the count went
from four copies to two — while the new comment claimed a fix to the match
reaches all of them at once. The match now lives in `transcripts.ts`, beside
the code that serializes the args, and both halves call it: the diff-read
half directly, the three brief/findings atoms through a wrapper that spreads
it over a record's call list. `certification.ts` already imported from
`transcripts.ts`, so this needs no layering inversion.

The helper also took the name of a module-private `namesPath` in
`utils/findings.ts` whose semantics are deliberately different — boundary
matching over PROSE, which credits `rm /plan/chunk-3.brief.md` for naming the
brief. Unifying the two the obvious way would make `openedBrief` credit an
agent for deleting a file it never opened. They keep distinct names now
(`serializedArgsNamePath`, `argsNameExactPath`), and both doc comments say
why, so a grep for "the path matcher" cannot land a reader on the wrong one.

Tests: the needle's own cases (whole JSON string value, no `.bak` sibling, no
shell mention) and the `rm <brief>` shape at the `openedBrief` atom. Relaxing
the needle to a bare substring now reddens BOTH suites from one edit — the
property the consolidation exists to buy, and the one the second copy denied.

* test(review): pin the shared needle's two untested arms, restore parseTranscript's doc

Three review Suggestions on the needle consolidation:

- `serializedArgsNamePath` had been inserted between `parseTranscript`'s
  JSDoc and its declaration, orphaning the doc onto the wrong function.
  Move it above the JSDoc so each doc sits with its own function.
- The diff-read half (`parseTranscript` with a `diffPath`) was exercised by
  no test — every `readTranscripts` call site omits `diffPath` and nothing
  set `diffToolCalls` non-zero, so an arg swap at the call site shipped
  green. Pin it: the exact diff read counted, a `.bak` sibling and a shell
  command that only names the diff refused, and `diffToolCalls: 0` without a
  `diffPath`.
- `argsNameExactPath`'s existential (`some`) was only ever run on 0/1-element
  arrays, so a first-element-only regression shipped green. Pin a match in
  the second position for `openedBrief` and `readBrief`.

* test(review): pin the diff read's RANGE, not only its count

The new diff-read fixture asserts `diffToolCalls`, which a mutation dropping
the `range: namedTheDiff ? rangeOf(args) : null` wiring survives — the count
stays right while every chunk-coverage ruling, which reads the lines rather
than the tally, is handed an empty list. The fixture's exact read now carries
an `offset`/`limit` and the test asserts `diffReads` equals `[[1, 40]]`;
dropping the wiring reddens it.
…wenLM#9532)

* chore(ci): Add --provenance to npm publish and id-token permission

* test(scripts): expect --provenance in npm publish step assertion

PR QwenLM#9532 adds --provenance to every npm publish in the release pipeline.
Update the workflow-pinning test to match the new command so the helper
test suite stays green.
wenshao and others added 26 commits August 25, 2026 01:58
…tainer (QwenLM#9556) (QwenLM#9723)

* feat(review): run the reviewed repository's own commands behind a container (QwenLM#9556)

A review executes the code it is reviewing. `build-test` runs whatever the
reviewed repository's `package.json` names — `npm ci` with its `preinstall` and
`postinstall` scripts, the build, the suite — and `test-efficacy` runs that
suite again once per baseline, control, mutant, hunk probe and revert. Both did
it as the invoking identity, and both handed the PR's code `process.env`
entire: on CI that carries `OPENAI_API_KEY` and `GH_TOKEN`. Reading them is one
line in a `postinstall`, and it needs none of the git-config machinery the
pipeline's threat findings are built on.

The boundary goes around the executions, not around the review agent. Wrapping
the agent was tried first and is the wrong shape: its secrets do not survive
the container's env allowlist, its `timeout` reaps the host-side client rather
than the container, its CLI version stops matching the runner's — and after all
of that the mount is the whole checkout, so `<repo>/.git` stays writable
anyway.

Three decisions the argv encodes, each measured rather than assumed:

- **The mount is the review temp dir, not the tree the command runs in.** The
  dependency farm links OUT of every tree — `exposeDependencies` points each
  package in the probe tree's `node_modules` at the review worktree's copy, 1
  722 of them on a live CI review. Mounting one tree would leave every link
  dangling. Every tree the pipeline builds is a sibling under `.qwen/tmp`, so
  one mount covers both ends while `<repo>/.git` stays outside it.
- **The environment is an allowlist**, not the inherited one.
- **The network is per command kind.** An install needs the registry; a build
  and a suite do not, and `--network none` keeps loopback so a suite that
  stands up a local fixture server still runs.

One ephemeral container per command. A long-lived one per phase would save
about 1–2% of the 540-second efficacy budget and would re-introduce exactly the
cross-run state QwenLM#9221 spent rounds closing.

Off by default: containerising a build by surprise changes what native modules
compile against. `review.sandbox` is `off` | `auto` | `required`, read through
`operatorReviewSettings` — which skips the workspace scope, so a repository
cannot ship a `.qwen/settings.json` that switches off the containment existing
to contain it. `QWEN_REVIEW_SANDBOX` outranks it so CI can require containment
without depending on a settings file the runner may not carry.

Each of the three decisions is pinned by a test that goes red when that
decision alone is reverted.

* chore(review): regenerate settings.schema.json for review.sandbox

The generated JSON Schema is checked in and CI diffs it against a fresh run
(`npm run generate:settings-schema`). Adding `review.sandbox` to
`settingsSchema.ts` without regenerating left the two out of step, which is
what the "settings.schema.json is out of date" gate is for.

The second red check, `Post Coverage Comment`, failed at "Download coverage
reports artifact" — a consequence of the test job dying before it uploaded
one, not an independent failure.

* fix(review): make `required` actually refuse, and fix four wiring bugs the first cut shipped

Five Criticals from the review, all of them real, and the first two would have
made the feature not work at all.

- **`required` failed open.** `sandboxVerdict` produced a `refused` verdict and
  nothing consumed it: both call sites tested `kind !== 'container'` and fell
  through to the direct spawn with the full environment. Refusal is now decided
  ONCE, at the top of each phase, before anything executes — which is also the
  only place that can cover the route that never reaches a spawn: a repo whose
  toolchain cannot be scoped is handed to the AGENT's own shell, and a gate at
  the spawn would leave that wide open under the very policy forbidding it.
- **`SANDBOX` was a shortcut past the policy.** The first cut returned `direct`
  when the session was already sandboxed, reasoning that the outer boundary is
  the one the operator asked for. Wrong for this property: the CLI's own
  sandbox constrains the filesystem and hands the child `process.env` entire,
  and stripping the secrets is half of what `required` promises.
- **The probe suite baked in the host's Node path.** `process.execPath` does
  not exist inside the image, so every sandboxed probe would exit 127 and map
  baseline, control, every mutant, every hunk and the revert to inconclusive —
  zero evidence exactly when containment is on. It uses the image's `node` now;
  the vitest path resolves because it lives under the mount.
- **The mount root took the first `.qwen/tmp`, not the deepest.** A review run
  from inside another review's worktree nests them, and the first occurrence
  widens the mount to the outer temp dir — pulling `<repo>/.git` and every
  sibling checkout in, which is the one property the mount exists for.
- **No UID/GID mapping.** The default image runs as root, so the container's
  writes into the mounted trees were root-owned and every later host-side
  cleanup — the install-timeout `rmSync`, `discardWorktree`, the sweeps — hit
  EACCES, accumulating residue across reviews.

Two things the fixes themselves needed, found by checking them rather than by
being told:

- **Refusing with `toolchain: 'unsupported'` would have caused the regression it
  was closing.** That value has a documented meaning — the brief reads it as
  "build-test could not scope this repo, install and build it yourself" — so a
  refusal routed into it would have sent the agent to run the reviewed code by
  hand, unsandboxed. It is a distinct `refused` now, with a brief rule that says
  the evidence is unavailable and must NOT be reconstructed by hand.
- **A bare `--user uid:gid` resets `$HOME` to `/`**, which the mapped user
  cannot write, so npm fails before the install starts — `utils/sandbox.ts`
  copies the host's `$HOME` for exactly this. The container gets a writable
  HOME inside the mount, with the npm cache under it.

The duplicated mount-root arithmetic became one exported `mountRootFor`, which
is both how the copies stopped drifting and how the nested case got a test.
Every fix above is pinned by a test that goes red when that fix alone is
reverted.

* fix(review): stop the reviewed repository from deciding its own containment (QwenLM#9556)

Round 2. The sharpest finding is that the guarantee this PR advertises was
true of one route and false through another.

**A repository could switch off the containment that exists to contain it.**
`operatorReviewSettings` skips the workspace scope precisely so a
`.qwen/settings.json` cannot set review policy — but the env layer that
outranked it is repository-controlled too: `loadEnvironment` walks up from cwd
and applies `<repo>/.qwen/.env`, from the very checkout under review, admitted
by default because folder trust starts off. `QWEN_REVIEW_SANDBOX=off` in a
committed `.env` disabled it. Three siblings were worse, because they have no
ordering to fall back on: `QWEN_REVIEW_SANDBOX_IMAGE` chooses the image the
reviewed code runs *inside*; `SANDBOX_SET_UID_GID=false` puts the container
back to root; `DOCKER_HOST` chooses which daemon answers, so `required` reads
as satisfied and whatever that daemon returns is scored as evidence.

`environment.ts` gains `isFileSourcedEnvKey`, and containment now reads only
the operator's settings or a real process variable. The policy additionally
only ever tightens, so even a genuine env value cannot lower a settings
`required` — which, as a mutation showed, is what actually protects the policy;
the file-source check is what protects the other three.

**`required` still failed open where the mount could not be built.** The gate
asked "did a runtime answer", never "can this phase be contained": with a
healthy daemon and a cwd outside `.qwen/tmp` — a `/review` of a local checkout
— the command fell through to the direct spawn with the full environment and a
report indistinguishable from a contained run. It asks the second question now.

**A refusal on `--resume` destroyed the run it was asked to continue.**
Returning a report let the handler's unconditional write overwrite the
in-flight one, and the refusal carries no run identity, so every later resume
failed the identity check even after the runtime recovered. It throws on a
continuation, which is the invariant the `!adapter` branch states in its own
words.

**The HOME added last round was itself cross-run state.** It lived on the
shared mount, and `sh -lc` sources `$HOME/.profile` while npm reads
`$HOME/.npmrc` — so one run's postinstall could plant what the next review's
install executes, with the network on. That contradicted this module's own
`--rm` "isolation by construction" claim, and it arrived with the fix for the
`$HOME` problem rather than in the original. HOME is a tmpfs now: discarded
with the container, never on the host. The npm cache stays on the mount, and
the comment says plainly that npm's integrity check is what stands between a
poisoned cache and a bad install.

**The mount root was lexical.** `resolve` never touches the filesystem, so a
symlink at or above `.qwen/tmp` — committable as mode 120000 — would have
widened a read-write bind mount to wherever it pointed. Every other creating or
destroying path in this pipeline refuses that; this one does now too.

Also: the new tests were platform-fragile in three places (Windows has no
`process.getuid`, and the documented `SANDBOX_SET_UID_GID` opt-out could fail
them on a developer's box), and the secret-leak check asserted against whatever
the runner happened to export rather than a planted canary. Both fixed, and the
mount-root tests now build real directories, which is how the symlink refusal
got pinned at all.

Every fix here is pinned by a test that goes red when that fix alone is
reverted — except the policy's file-source check, which a mutation showed is
redundant with the tightening rule, and which is documented as defence in depth
rather than claimed as load-bearing.

* fix(review): close the hand-off arm, and three fixes that were wrong at their edges

Round 3. Six Criticals, all of them real, and two were introduced by round 2's
own fixes.

- **The hand-off arm of the original blocker was still open.** The phase gate
  refuses when containment is impossible — but a repo the npm adapter cannot
  scope (yarn/pnpm/bun, no lockfile) reaches `unsupportedReport` with the gate
  satisfied: a runtime answered and the tree is mountable. That report tells the
  agent to install and build with its own shell, which nothing here contains. An
  inapplicable adapter is now a refusal under `required`, not a hand-off.
- **The mount-failure refusal fired under `auto` too**, and its message
  hardcoded `required`. Under `auto` the contract is "contain it when that is
  possible", so an unmountable tree falls back to the direct spawn — refusing
  there would have taken build/test and efficacy evidence away from every local
  review the moment a daemon happened to be running. Mine, from round 2.
- **`--workdir` got the lexical path while the mount got the realpath.** Round
  2 made `mountRootFor` canonicalise; the workdir did not follow, so on any
  layout where the two spellings differ — `/var` against `/private/var` is the
  everyday one — the container was handed a directory it does not have and
  every command would fail before starting. Also mine, from round 2.
- **The daemon scrub missed the indirection selectors.** `DOCKER_CONFIG`,
  `CONTAINERS_CONF` and friends name a config file that in turn names the
  daemon, the registries and the runtime: scrubbing the direct selectors and
  leaving these moves the same steering one level down.
- **`isFileSourcedEnvKey` matched case-sensitively.** Windows env lookup is
  case-insensitive, so a `.env` committed as `docker_host=…` reaches the child
  exactly as `DOCKER_HOST` would while the exact-case test answers "not from a
  file" about a value that is. Same class as `sanitizedGitEnv`'s case fold.

The gitfile finding stays deferred with its reasoning in the thread — it is not
a hole this PR opens, and closing it belongs to the identity gates rather than
to the sandbox.

Two mutations came back green on the first pass and were the useful part of
this round: the `DOCKER_CONFIG` scrub had no test because the fixture pinned one
key rather than the set, and the operator's `SANDBOX_SET_UID_GID=false` opt-out
had no test at all — both uid tests asserted the flag was PRESENT. The scrub
test now asserts the whole set, and the opt-out has its own.

* fix(review): the hand-off gate was dead code, and three more edges

Round 4.

- **Last round's hand-off gate never ran.** It tested `!applicable`, and
  `applicable` is the filtered adapter ARRAY returned by
  `selectToolchainAdapter` — never falsy. The gate shipped green and closed
  nothing. It is judged on the RESULT now (`toolchain === 'unsupported'`),
  which also covers the second route to a hand-off — an adapter that applies
  and cannot scope, from inside the npm one — and the predicate is exported and
  tested rather than living inline where no test could see it.
- **The boxed farm dangled under a symlinked ancestor.** Round 3 made the mount
  and `--workdir` canonical; `exposeDependencies` still built its link targets
  from the lexical root, so on the everyday macOS `/tmp` → `/private/tmp`
  layout every farm link resolved to a path the container does not have. The
  phase would then report "every file was red or collected nothing" — a wiring
  failure published as a statement about the PR's own suite. Canonicalised on
  the sandboxed path only; the direct path keeps the caller's spelling.
- **A timed-out boxed run leaked its container.** `--rm` fires only on a
  self-exit, and the deadline kills the runtime CLIENT — so a suite whose own
  trap ignores the forwarded signal keeps running with the review temp dir
  writable, past the budget and past the end of the review. Containers get a
  unique `--name`, and both spawn sites `rm -f` it when the deadline fires.
- **The daemon scrub deleted case-sensitively.** Round 3 taught
  `isFileSourcedEnvKey` to fold case on Windows and left the deletion exact —
  so a `docker_host` written by a repo `.env` was correctly detected and then
  not removed.

Two mutations came back green again, and both were the round's real lesson: the
hand-off refusal had no test (which is how its dead-code predecessor shipped),
and the farm canonicalisation still has none — it needs a symlinked-ancestor
fixture with a live runtime, which this machine cannot provide, and it is
listed with the other integration gaps rather than claimed.

* fix(review): put the hand-off conversion at the one exit, and make the reaper reachable

Round 5. The first finding is the same one for the third time, and the third
time is the one worth explaining: I had been guarding routes rather than the
exit.

- **Attempt one** tested `!applicable` — the filtered adapter ARRAY, never
  falsy — and was dead code.
- **Attempt two** wrapped the two `adapter.run` returns and missed the
  `!adapter` branch's own `unsupported` report.
- **Now** the conversion sits at the single place a report can reach a caller,
  and it is an exported `applyHandOffPolicy` rather than a branch inside a long
  function, so a test can reach it without a live container runtime. Both
  previous attempts failed the same way — a guard placed where no test could
  see it, in a function with more exits than the author was holding in mind.

**The container reaper added last round was unreachable, not wrong.**
`spawnSync` sends its `killSignal` at the deadline and then WAITS for the child
to exit, so an attached runtime client that forwards SIGTERM to a workload
whose own trap ignores it never returns — and the `killContainer` after it never
runs. The boxed spawns use `killSignal: 'SIGKILL'` now: the client cannot ignore
it, the call returns, and the container is reaped by name at the daemon, which
is where the deadline had to be enforced.

**The scrub missed the proxy family.** `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`/
`NO_PROXY` (and their lowercase spellings) are honoured by both clients for
every daemon call, so a repo-shipped one interposes on the connection the
direct selectors were scrubbed to protect.

One test was removed rather than added: a `expect(killContainer).toBeTypeOf(
'function')` I wrote to "document" the SIGKILL reasoning. It asserts nothing and
would have read as coverage; the reasoning belongs in the comment where it now
lives alone.

The `killSignal` choice is reasoned but not pinned — it needs a live runtime and
a TERM-ignoring workload — and joins the integration gaps already listed in the
PR body rather than being claimed.

* fix(review): the third continuation exit destroyed the report it was continuing

Round 6, two Criticals: the deferred gitfile one, and this.

Round 5's single-exit conversion runs on `--resume` answers too. A resumed
report whose toolchain is `unsupported`, under a policy that tightened between
the first call and the continuation, was replaced by a fresh refusal — which
the handler writes unconditionally, over the report the call was asked to
continue. That refusal carries no run identity, so every later `--resume` fails
the identity check and the round redoes install, build and every suite.

"A continuation must never answer with a FRESH report" is enforced by a throw
at the refusal gate and at `!adapter`. This conversion was added after both and
did not have it. It does now.

The trigger is ordinary rather than adversarial: the policy is read per call,
so an operator raising it — or a workflow's `env:` — between call one and the
resume is enough, on exactly the unscopeable repo shapes (yarn/pnpm/bun) that
reach a hand-off at all.

**The first test I wrote for this passed without the fix.** It drove
`runBuildTest` with an incomplete argument object and asserted `.toThrow()`;
the throw it saw was `--timeout must be a finite number of seconds`, from
validation long before the code under test. A mutation caught it. Making the
fixture faithful means reproducing the whole resume-identity apparatus, so the
decision is a predicate instead — `resumeWouldDestroyReport`, tested directly,
with the wiring left as one visible line. Same shape as round 5's
`applyHandOffPolicy`, and for the same reason: a guard a test cannot reach is a
guard that has already been wrong twice here.

* fix(review): reap the container on every abnormal exit, not only the timeout

Round 7, one Critical.

`build-test` reaped the boxed container when `spawnTimedOut(r)`, which is true
for ETIMEDOUT and false for a `maxBuffer` overflow — and a reviewed command
writing 64 MB to one stream is a postinstall away. The client dies with
ENOBUFS, the reap is skipped, and the container keeps the review temp dir
mounted read-write past the per-command deadline and past the end of the
review.

The sibling in `test-efficacy` already reaped on the broader `r.error ||
r.signal`. That the two had drifted to different conditions is how one came to
miss a case the other caught, so they now share one exported predicate:
`status === null` — exactly "the client did not exit normally", covering
ETIMEDOUT, ENOBUFS and signal kills in one condition rather than a list of
causes to keep in sync. A normal exit needs no reaping (`--rm` has fired), and
a client that never spawned has no container, where the reap is a silent no-op
by `killContainer`'s construction.

* fix(review): scrub the container client env by provenance, not by name

The scrub list named the daemon selectors, then the proxy family, and still
missed DOCKER_API_VERSION — which selects no daemon at all. It just makes
every client call fail, and under `sandbox: auto` a failing availability
probe reads as "no runtime here", so a repository that ships one value in
its .env turns containment off without a word.

The class has no last entry. Replace the list with the rule it was trying to
approximate: a key the loader wrote from a file the reviewed checkout
supplies does not reach the process that decides whether containment
happened. The client needs no repository-provided environment for anything,
and the workload's environment is an allowlist either way.

Deleting restores rather than approximates: the loader records a key as
file-sourced only where the real environment had nothing, so a file value
never shadows an inherited one and the client keeps the operator's own
DOCKER_HOST, PATH and HOME.

* fix(review): drop --user on rootless runtimes, and take the base-side rerun into the box

Two blockers from round 9.

R9-1. `--user uid:gid` is right on a rootful engine and wrong on a rootless
one. Rootless podman — its default install — and rootless docker run the
engine inside the invoking user's namespace, so container uid 0 IS the
invoking host user and every other container uid lands on a subuid near
100000. The availability probe answers fine, the phase gate passes, and then
every boxed command meets a tree it does not own: `npm ci` cannot create
node_modules, and what the container does create comes out unsweepable — the
cross-run residue `--user` exists to prevent. Detect rootlessness from the
runtime's own `info` document and drop the flag there, where the container's
root is already the invoking user and the flag has nothing left to do.

Unknown answers rootful. The two wrong guesses are not symmetric: guessing
rootful on a rootless host breaks the run loudly, guessing rootless on a
rootful one silently runs reviewed code as real uid 0 on a writable mount.

R2-21. test-delta kept a private copy of build-test's run(). The copy was
correct until build-test's grew a container, and then the two sides of the
measurement stopped being comparable: the PR side in the image with an env
allowlist and no network, the base side on the host with both. A test that
reads an env var or opens a socket flips on one side, and the file whose only
job is to say which side a failure belongs to says "the PR's". The duplicate
is deleted rather than re-synchronised, and `required` now refuses the
base-side rerun instead of running it on the host.

A refusal returns an empty report, so the brief now says in as many words
that an empty `netNew` beside such a note is the absence of a measurement,
not the absence of a regression.

* fix(review): refuse mount roots the -v grammar cannot spell, and stop the suite inheriting the operator's policy

R10-1. `-v src:dst` has exactly one separator, so a checkout at `/…/my:repo`
produces a spec docker answers with `invalid spec … too many colons`.
`mountRootFor` called that root mountable, which bypassed both designed
degradations: under `auto` every install/build/test command surfaced a raw
mount error the report attributed to the PR instead of falling back to the
direct spawn, and under `required` the phase gate passed and the refusal that
should have explained it never happened. Classify such a root as unmountable
and it rejoins the path every other unmountable root already takes.

Measured rather than assumed, including the suggested alternative: `--mount
type=bind` fails on the same colon AND on a comma that `-v` takes without
complaint, so it would trade one unspellable path for two.

On Windows this refuses every absolute path, deliberately: a drive letter is
a colon, and a mount whose source and target are the same path cannot be a
Windows path at all. Containment is not available there, and saying so is
what gives `auto` its fallback and `required` its refusal.

Hermeticity, which this PR broke. The phase gates read the operator's own
`review.sandbox`, so a maintainer who turns the feature on and runs the suite
watched 101 review tests report that setting back at them instead of what
they measure. Both routes are closed: the shared setup drops the environment
variables, and the two suites whose gates resolve settings isolate `QWEN_HOME`
the way they already isolate the host git config — measured at 101 → 0 from
each route independently. test-delta's gate is injectable for the same reason
its `exec` is.

Also from the round-10 list, all self-inflicted: the `--user` test pinned the
documented opt-out it asserts against, `runtimeIsRootless`'s unknown case is
now structural (an empty document carries no marker) rather than a literal a
mutant can flip, and the dead `resetContainerRuntimeProbe`/`force` pair is
gone. Two doc corrections: `containerPathFor` names the null it actually
produces, and the brief's `refused` bullet covers all three routes that
produce it rather than one.

* test(review): gate the mount-root cases Windows cannot be asked

Round 10 refused every absolute Windows path from `mountRootFor` — a drive
letter is a colon — and said so as intended behaviour. It is; what that reply
missed is that this file then asserts the opposite. Three cases here turn on a
root being MOUNTABLE, which is the one question Windows has no answer to: the
comma control added last round, the deepest-temp-dir selection, and the
symlink refusal's honest half. All three assert non-null against real
absolute paths, and the colon case also builds `my:repo`, a component name
Win32 rejects outright.

`test_windows` is merge_group-only and a required check, so nothing here
would have gone red until the merge queue itself.

Gated off win32 with the convention this directory already uses in 37 places,
and the shipped semantics pinned by a win32-only case rather than left to the
comment that describes them.

* test(review): make the Windows pin actually reach the check it pins

The win32 case added last round named a path that does not exist, so
`mountRootFor` returned null out of its realpath catch and the assertion held
whether or not the drive-letter check was there at all — probe-verified: the
layout is absent and the answer is null anyway. It pinned nothing.

Build the layout first and assert it exists, so the catch is closed and the
colon is the only thing left that can produce the null.

Two more from the same list. The uid cases returned early instead of
skipping, which reports PASSED with zero assertions on the lane where the
condition bites — the reading "this held" for something never checked. And
the install-network assertion looked only for a bare `none`, blind to the
joined `--network=none` a refactor could switch to; it now rejects the flag in
either shape.

* test(review): skip the second uid case instead of returning, and restore its stub in finally

The sibling got `it.skipIf` last round and this one did not, so on a lane
where `getuid` is undefined it still reports PASSED having asserted nothing —
which reads as "this held" for something never checked.

Its `SANDBOX_SET_UID_GID` stub was also cleared on the last line rather than
in a `finally`. There is no `unstubEnvs` in the vitest config and no
file-level hook that would catch it, so a failing assertion left the stub set
for whatever ran next — one red test quietly becoming two.

* test(review): drop the comment last round's edit left stranded

Wrapping the uid case in try/finally moved its opt-out note above the stub
and left the original copy inside the try, so the same three lines now appear
twice a few lines apart. Delete the stranded one.

* test(review): pin the four decisions this feature is sold on

Four cells the suite reached only by accident of the machine it ran on, each
carrying a live mutant on a property the PR description states as a
guarantee. All four functions already took their ambient dependency as an
injectable parameter, so this is assertions, not seams.

- sandboxPolicy: strictest-wins in BOTH directions, and a file-sourced value
  counting for nothing. "A repository cannot switch off the containment that
  exists to contain it" was described in a comment and asserted nowhere;
  env-overrides-settings, a looser strictest, and a dropped file-sourced guard
  all shipped green.
- sandboxVerdict: `auto` with nothing answering runs DIRECTLY. Every other
  case here either has a runtime or is `required`, so a mutant refusing
  instead — turning `auto` into `required` on every machine without docker —
  survived.
- refuseUnsandboxedPhase: the PASS path. Every other assertion about this gate
  is a refusal, so unconditional refusal under `required` — every review on a
  perfectly good host — survived.
- containerPathFor: it feeds `--workdir` at both spawn sites, and had no test
  at all. A lexical spelling names a directory the container does not have;
  the parent fallback is what lets a probe tree be named before it is built.

Mutation-checked, one at a time: seven mutants, seven reds.

* fix(review): read the policy setting the way an operator writes it

`"Required"` in settings.json — or a stray trailing space — matched no policy,
resolved to `off`, and disabled the containment the operator had just asked
for. Silently. The environment value was already normalised; the settings
value was not, and the asymmetry fell on the wrong side: settings is the
documented place to turn this ON, since the environment can only tighten. So
the unnormalised half was the half operators actually use, and a fail-open on
the one setting whose whole purpose is to fail closed.

Reported as a suggestion in rounds 8, 9 and 16 and deferred each time under
the critical-only posture. Measured this round: both `"Required"` and
`"required "` returned `off`.

Also pins two shapes the new tests left open. `sandboxPolicy`'s settings
default is the production path every real caller takes, and with it swapped
for `{}` the whole settings half stopped being consulted while every
assertion — each passing settings explicitly — stayed green; it is now driven
through an isolated settings file. And the `SANDBOX_SET_UID_GID` opt-out
parses case- and space-insensitively, which nothing asserted.

The remaining deferral, a fixture literal pasted twice, is duplication rather
than a defect and is left alone.

* test(review): finish the policy table instead of one row of it

Last round's normalisation fix was asserted only against `required`
spellings, which is the shape a mutant keyed on that one value walks
straight through — leaving an operator's `"Auto"` resolving to `off`, the
same silent downgrade one rung lower. Assert every policy on both sides.

Two adjacent cells with it. A garbled ENVIRONMENT value must be dropped on
its own rather than taking the operator's setting down with it: the
environment is the half a repository can reach, so garbage there must never
answer for the half it cannot. And `sandboxPolicy`'s env default is the twin
of the settings default pinned last round — every assertion here hands it an
env literal, so `env = {}` as the default stops the environment half being
read at all and nothing notices.

Three mutants, three reds.

* test(review): exercise the reap and the runtime probe, and stop leaking a fixture

Three of the four recorded this round; the fourth is duplication, not a
defect, and is left alone for the third time.

`killContainer` is the whole answer to a container that outlived the client's
deadline, and nothing anywhere ran it — a garbled argv, or a dropped `-f`,
shipped green, and what survives is a container holding the review tree open
past the end of the run. It now takes its spawn as a parameter, so the argv
and the swallow-and-continue contract are both asserted.

`containerRuntime`'s probe had the same hole with a memo and a real daemon
call in the way. The decision is now separated from both: `firstAnsweringRuntime`
is order and nothing else, which is the content — a client installed but not
running must never shadow one that is.

And the `containerPathFor` case left a temp tree behind on every run. In a
change about not leaving residue behind, that one is just embarrassing.
)

* feat(core): add a per-project outside-repo artifact landing

`/audit` (landing in follow-ups) writes reports, sidecars, and intermediates
that quote the audited module verbatim. When the audited repository's ignore
state cannot keep those out of version control, they have to land somewhere
the repository can never commit — this is that landing: per user and per
project under QWEN_HOME, 0700, so quoted (possibly exploitable) module
content stays private.

Adoption is validated rather than assumed. The path is fully predictable —
the project hash is a pure function of the root — and 0700 does not exclude
the user's own other processes, so an existing leaf is not evidence this
tool created it. A leaf planted as a symlink is refused, and a permissive
mode is tightened, since mkdirSync's mode only applies to directories it
creates.

The contents are validated too, because artifacts land BELOW the leaf and an
O_NOFOLLOW open only guards the final component: a symlink child is a
complete escape (mkdirSync treats a symlink-to-directory as the directory,
so everything written "inside" goes wherever it points) while the leaf keeps
passing every check on itself. A hardlinked file is the same story through
O_TRUNC. Both are refused. The landing is reused across runs — the report
and its sidecar are the durable artifacts — so a merely non-empty landing
stays adoptable.

* fix(core): validate every component of the fallback landing path

The leaf was validated but the path walked to reach it was not, which left
the whole landing relocatable by a single symlink.

mkdirSync(recursive) follows symlinks in every component ABOVE the final
one, and lstat refuses to follow only the final one — so a leaf-only check
cannot see a redirected parent. Planting `audits` needs one `ln -s` and no
race, because ~/.qwen exists long before `audits` does: the leaf is then
created inside the planter's directory, reports as a perfectly real
directory, passes the contents check, and every artifact written "into the
landing" lands wherever the link points. Probed against the built module:
before, the landing resolved inside the planted directory and a written
report appeared there; after, the call refuses by name and the planter's
directory stays empty.

Each component this method creates is now created non-recursively and
adopted only after its own lstat — recursive creation is precisely what
walks a planted component unchecked. QWEN_HOME itself stays unvalidated: it
is the user's own configured location, not a path this method invents.

Also closes the unknown-dirent-type gap in the contents check: on a
filesystem that does not report entry types, isSymbolicLink() and isFile()
both answer false, so an entry slipped past every arm; those entries now get
an explicit lstat.

* fix(core): close audit landing adoption escapes (QwenLM#9776)

Adoption now normalizes the full mode — a 0300 plant kept owner write
and blinded the content check while writes still succeeded — and an
unlistable landing fails closed instead of skipping validation. The
landing is refused before creation when QWEN_HOME resolves inside the
audited repository, a missing QWEN_HOME base is created instead of
throwing ENOENT, and the mutating getter is renamed to
ensureAuditFallbackDir so the filesystem side effect is visible.

* fix(core): close audit landing race seams with re-validation (QwenLM#9776)

* fix(core): harden audit fallback validation per review (QwenLM#9776)

* fix(core): re-check landing contents in pre-return re-validation (QwenLM#9776)

* fix(core): drive landing content check from fresh per-entry lstat (QwenLM#9776)

The content check decided typed dirents' entry type from the readdir
snapshot while consulting a fresh lstat only for nlink, so a same-UID
swap of a listed entry for a symlink between the snapshot and the loop
passed every arm and returned a landing holding a symlink child. Lstat
every entry once and drive the symlink, directory, special-file, and
hardlink arms from that single fresh stat — the same single-stat shape
the unknown-dirent-type path already used, so the two paths no longer
disagree about what "validated" means.

* fix(core): re-lstat audit landing after follow-based validation steps (QwenLM#9776)

* fix(core): fold case in audit landing containment on case-insensitive platforms (QwenLM#9776)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
* fix(web-shell): reduce streaming thought render jank

* test(web-shell): expand historical question result

* fix(web-shell): address streaming review findings

* test(web-shell): strengthen streaming review follow-up tests

* test(web-shell): pin structural snapshot opt-in and matched insight path (QwenLM#9914)

---------

Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
…9794)

* feat(review): report findings to clients as a typed contract

Add a report_findings core tool: one {level, findings[]} call whose
field names and enum spellings match the review findings artifact, so
/review hands live clients (TUI, Web Shell transcript, ACP hosts) the
findings as data instead of a Markdown restatement. The tool sorts by
severity/confidence/location, derives shortSummary (<= 60 chars) for
compact list UIs, and refuses duplicate ids, control characters, and a
partial outcome set - after --fix the skill re-issues the call with a
fixed/skipped/no_change_needed outcome per finding, mirroring the
artifact's own --outcomes completeness rule, and the rule stays live
for later in-session disposition changes.

The finding enums now live in core; packages/cli/utils/findings.ts
re-exports them under its historical names (the Web Shell renderer
keeps its deliberate browser-side copy). The TUI renders the new
findings_list display as per-finding rows with severity color, id,
file:line, confidence marker and outcome badge; the daemon TUI adapter
passes it through and history/recording compaction truncates only the
free-text fields.

* fix(i18n): cover report_findings in tool display-name maps

The two CI drift gates caught what the feature commit missed: the CLI
requires a zh translation for every core tool display name, and the
web-shell requires a display-name entry (and its own zh translation)
for every core wire tool name. Add toolDisplayName.ReportFindings to
the zh/zh-TW/ca/en locales, report_findings to the web-shell
TOOL_DISPLAY_NAMES map, and toolName.report_findings to the web-shell
zh strings.

* test(cli): pin ToolMessage routing for findings_list displays

Stage-2 review observation: FindingsDisplay had direct render tests,
but nothing pinned the ToolMessage discriminator, so removing the
routing branch kept every test green while findings fell through to
the JSON-string fallback. The new case asserts the joined file:line
row and the low-confidence marker, which the fallback never produces;
verified by mutation (disabling the branch turns the test red).

* fix(review): address automatic-review round 1 on the findings contract

Two behavior fixes: compressFindingSummary backs its hard cut off a
surrogate pair instead of emitting an unpaired high surrogate, and
sortReportedFindings now matches the artifact's sortFindings exactly
(code-unit file/id comparison, missing line ranked first) as its doc
comment already claimed. SKILL.md Step 6 gains the bounded-contract
rule: the tool refuses over-cap calls whole, so an artifact past 50
findings reports the most-severe 50 with the cut disclosed, and
over-cap prose is shortened rather than dropped.

The rest closes the mutation gaps the review demonstrated: fixtures
where shortSummary differs from summary (pinning that rows render the
compact label), the summary blank-guard case, exact-value assertions
for shortSummary derivation and the word-boundary cut, per-field
control-character coverage, line/id passthrough, and the artifact-order
tiebreaks (missing line first, id by code units under a stable sort).
Every new assertion was mutation-verified: each documented mutant now
turns at least one test red.

* test(core): pin code-unit file/id sort order in report_findings

* fix(review): close report_findings contract gaps from review round 5 (QwenLM#9794)

- Align the tool's `file` cap with the artifact path domain (PATH_MAX,
  4096) and refuse line numbers outside JavaScript's safe integer range.
- Refuse `outcome: "skipped"` without a non-empty `outcomeNote`, in the
  tool and in the `review findings --outcomes` ledger that feeds it.
- Hold an outcome re-report to the active report's identity — same ids,
  none dropped, none added — so a partial fix run cannot silently
  shorten the client's list.
- Render the report-level `level: "low"` state in FindingsDisplay and
  sanitize every interpolated row value to one terminal-safe line.
- Validate the full findings_list shape at the daemon TUI boundary and
  fall back to plain text for malformed payloads instead of crashing.
- SKILL.md: extend the sanctioned over-cap shortening to `outcomeNote`
  on the Step 6B re-report; the artifact keeps the full-length text.

* fix(review): implement report_findings replacement semantics and close round 6 contract gaps (QwenLM#9794)

* fix(cli): close findings_list boundary bypass and restore superseded reports on rewind (QwenLM#9794)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.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>
…nLM#9871)

* fix(ci): neutralize legacy ##[ commands in autofix stdout echoes

A review finding about workflow-command injection carried its payload
strings (::error::forged, ##[add-matcher]) verbatim as example text.
Prepare echoed feedback.md raw, the runner parsed the mid-line
##[add-matcher] and failed the step before the agent ran, and the
consecutive-failure breaker burned the takeover window of QwenLM#9761 in
about 70 minutes while reporting it as a 100/100 round cap.

The existing neutralization covered only the modern :: syntax; the
runner also parses the legacy ##[name] form, even mid-line. Extend
every untrusted-content echo in the autofix family (prepare feedback
echo, both artifact dump loops, the gate's failure/handoff/no-action
echoes, the deferred-findings dumps and upsert re-emit loops) to
neutralize both prefixes, and pin the census plus a behavioral ##[
case in the contract tests.

* fix(ci): unify the autofix neutralizer on one canonical spelling

Review feedback on QwenLM#9871: the two upsert re-emit loops neutralized with
a hybrid spelling (bash expansion for :: plus a single-syntax sed for
##[) while the other fourteen sites used the canonical two-expression
sed, establishing two implementations of one normalization. A future
edit fixing one spelling could miss the other and recreate the
single-syntax half-guard that let QwenLM#9761 through. Unify both loops on
the canonical sed and consolidate the pinned test shapes.

The census test's comment also promised more than its assertions
deliver: it pins the known enumeration, it does not auto-detect new
un-neutralized echoes. Reword it to state the actual guarantee, ban
the ##[-only sed and the bare bash expansion alongside the existing
::-only ban, and fold the script-side re-emit loop into the census.

* test(ci): assert the neutralizer pairing property instead of banning shapes

The three shape bans only matched the no--e spellings, so the canonical
line's natural half-copies (sed -e 's/::/;;/g' alone, or the ##[-only -e
mirror) passed every ban while guarding only one syntax. Assert the
pairing property instead: every occurrence of either substitution
expression must belong to the canonical two-expression pair, so a
half-guard in ANY spelling unbalances the count and fails the census —
verified by probe against all four half-guard shapes.

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…nLM#9873)

* fix(core): refuse a workflow script before it can spend anything

`WorkflowRunner.start` minted a runId, opened a journal and registered the
run before a single byte was parsed; the first compile happened downstream
inside the sandbox. One TypeScript annotation therefore produced a fully
registered, failed run — a phantom row in `/workflows`, a snapshot on disk
and a telemetry event, for a workflow that never began.

Compile first. `compileWorkflowScript` is exported from the sandbox and
called by `start()` before anything is minted; the sandbox's own `run()`
now calls the same function rather than a lookalike, because a gate that
compiled even slightly differently from the run would wave through scripts
that then fail after registration, which is the failure the gate exists to
prevent. A refusal throws `WorkflowScriptNotLaunchedError`, which the tool
turns into a not-launched result rather than a workflow failure: there is
no runId to hand back, and the model should re-send a corrected script
rather than go looking for a run that does not exist.

The message points at the author's own line. V8 already renders the
offending source line with a caret under the exact column and puts it at
the head of the stack, so reuse that instead of reconstructing a caret from
column numbers, and correct the one thing it gets wrong here: the IIFE
wrapper shifts every body line by one, so V8's line 2 is the script's line
1. Long lines are windowed with the caret shifted to match. The hint that
follows names the usual cause, which is TypeScript syntax.

The wrapper also gains `'use strict'`, sharing its line with the arrow so
the line offset stays at one. In sloppy mode an undeclared assignment in
model-authored code silently created a property on the sandbox global
instead of throwing.

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

* fix(core): preserve workflow compile line numbers

* fix(core): bound workflow compile error lines

* fix(core): preserve workflow compile error frames

* fix(core): clarify incomplete workflow syntax

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
…nce signal (QwenLM#9903) (QwenLM#9938)

* feat(review): engage the severity floor early on a sustained convergence signal (QwenLM#9903)

The convergence diagnosis prints the remedy — drop to a critical posting
floor — whenever the first-time-finding rate stops falling, but the floor
itself engaged on a fixed round-6 schedule, so rounds 3-5 kept posting
Suggestions inline at full cost while re-deriving the same root-cause
cluster.

Record the consecutive not-falling rounds in a new ledger field
(flatRounds); at two — the shortest window in which "not falling" is an
observation — engage the floor on the firing round, ahead of schedule,
latched for the rest of the loop, and disclosed in the posted body with
the streak that armed it. The streak rides the churn streak's trust group
(foreign markers stripped at the recovery seam, planted values clamped to
the rounds the PR actually ran); an explicit suggestion floor still
overrides the latch and an unknowable round still fails open.

Fixes QwenLM#9903

* fix(review): gate the early-floor streak measurement on the auto posture (QwenLM#9903)

* docs(review): align the early-floor docs with the shipped behavior (QwenLM#9903)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(serve): repair persisted session lifecycle

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

* codex: fix CI failure on PR QwenLM#9626

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

* codex: fix CI failure on PR QwenLM#9626

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

* codex: address PR review feedback (QwenLM#9626)

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
… read (QwenLM#9753)

* fix(cli): degrade gracefully when messageRewrite.promptFile cannot be read

* docs(cli): document rewrite prompt fallback
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
…fety comment (QwenLM#8861)

* test(core): pin transport retry diagnostics and correct the replay-safety comment

Follow-up to the QwenLM#7938 maintainer verification, addressing both
non-blocking findings.

The comments justifying the thinking-phase replay claimed thought parts
are never recorded in history. That is not the invariant: the successful
attempt's thoughts are recorded. What makes the replay safe is that the
failed attempt's accumulated partial turn is discarded wholesale before
the retry (popPendingPartialAssistantTurn) and thought parts are never
user-visible content. Both comment sites now state that.

The two retry diagnostics were unpinned: hardcoding
yieldedNonContentChunks on the scheduled log or relabeling the
skipped_after_content decision on the not-taken log survived the suite.
The scheduled-log field is now asserted in the thinking-only replay
test, and a new test covers the path on current main that still emits
skipped_after_content — a cut after a delivered functionCall, where the
replay gate and the continuation gate are both closed. Each mutant now
fails exactly one test.

* test(core): close the review's four gaps on the retry diagnostics

Address the inline review on the follow-up:

- Correct the last surviving copy of the stale replay-safety rationale
  (the thinking-phase test's header comment still claimed thoughts never
  enter history).
- Hoist socketCut/cutAfter out of the continuation suite and reuse them
  in the function-call cut test instead of a character-for-character
  inline copy, so the transport error shape has a single producer.
- Pin the false side of yieldedNonContentChunks in the tool-preparation
  retry test, and the 'exhausted' arm of retryDecision in the budget
  exhaustion test. Hardcoding the diagnostic true or collapsing the
  ternary now each fail exactly one test.

* test(core): unify socket-cut fixtures and pin retry exhaustion with thinking (QwenLM#8861)

* docs(core): ground thinking-replay safety in the partial-turn discard (QwenLM#8861)

* fix(core): ground replay rationale and pin retry-decision ternary (QwenLM#8861)

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

---------

Co-authored-by: ComplexSimply <rudy.arrowsong@gmail.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
…Tauri upgrade bridge (QwenLM#9085)

OpenWork (modelstudioai/openwork) has forked the Electron desktop code and is
self-contained now, so retire the Electron package and its release/sync
machinery from this repo:

- Delete packages/desktop (Electron app, live-host app, bun workspace).
- Retire scripts/desktop-openwork-sync.ts and the desktop-openwork-sync root
  script; the OpenWork sync is no longer needed.
- Retire .github/workflows/live-host.yml, live-host-release.yml and
  sync-live-host-to-oss.yml; live-host releases now live in OpenWork. The
  CLI-side packages/cli/src/serve/live code stays for now (separate cleanup).
- Retire scripts/check-voice-guard-sync.js (cli<->desktop parity only) and
  its CI step.
- Clean up remaining references: root package.json workspaces negation and
  package-lock.json, eslint/prettier/yamllint ignores, architecture docs,
  web-shell skill descriptions, and review-lib workspace fixtures/comments
  (renamed to point at packages/desktop-shell, the remaining negation).

Deliberately kept: the Electron->Tauri upgrade bridge — desktop-release.yml
(incl. the electron_bridge input), create-electron-bridge-manifest.mjs,
sync-desktop-to-oss.yml (mirrors Tauri desktop-shell artifacts only) and
everything under packages/desktop-shell.
…M#9840)

* fix(goal): resume an evidence-limited Goal from a fresh window

A Goal stopped at one of the evidence bounds was the only Goal state the
reducer refused to resume: `/goal resume` threw, and the sole way forward was
to edit or replace the objective — which mints a new revision and, for a
replace, a new Goal. An unattended run therefore ended for good, and the user
was told to rewrite the objective they had not changed their mind about.

Resume now restarts the evidence window instead. The objective, the revision
and the accumulated turn count carry over; the cursor moves to the resume
boundary and the checkpoint is dropped. That is the same reset `/goal edit`
already performs, minus the parts a resuming user did not ask for.

Keeping the exhausted window would only exhaust it again on the next turn,
which is why the refusal existed at all. Dropping it has a real cost and it
belongs to the user who asked to resume: evidence recorded before the resume is
no longer citable, so a terminal proposal must prove itself from what the
resumed run produces. Only the enumerated evidence bounds reset the window — a
`usage_limited` Goal stopped by a transient operational failure keeps its
cursor and checkpoint, since nothing was wrong with them.

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

* fix(web-shell): offer resume for evidence-limited goals

* refactor(goal): retire the client evidence-limit mirror, pin the resume cursor

The previous commit made `canResumeGoal` status-only, which left the whole
client-side evidence-limit apparatus -- `isGoalEvidenceLimited`, the
`GOAL_EVIDENCE_LIMIT_REASONS` sentinel mirror, and the cross-package drift
guard that kept the mirror honest -- consumed by nothing but its own tests.
Delete it instead of documenting it: the reducer resumes an evidence-limited
Goal by restarting its window, so no client decision hangs on telling those
stops apart, and a sentinel copy with no consumer is pure drift surface. The
component tests keep one legacy-prose case each, with the sentinel inlined as
inert fixture text.

Also close the review findings on the same review round:
- mappers.test.ts justified carrying `limitKind` with the old refusal story;
  restate the pin as what it actually guards (a field-by-field mapper rebuild
  silently narrowing the record).
- The fake goal journal answered `getTranscriptCursor` with a fixed
  `{ recordId: null }`, which made the resume-cursor assertion pass trivially
  -- and null is the one value the real evidence pipeline hard-rejects
  (`cursor_unset`). It now answers with the transcript tail, as the real
  journal does, and the test asserts the resumed cursor IS that tail.

Mutation probe: reverting the fake to `{ recordId: null }` fails exactly the
resume-cursor test (108 others green).

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(core): allow scoped Git credential config

* fix(core): handle Git config URL false positives

* docs(core): clarify Git client safety invariant

* fix(core): reject unsafe Git extension inputs

* docs(core): reattach cloneFromGit JSDoc and note blocklist mirror (QwenLM#9870)

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

* fix(core): inherit wrapper environment for restricted Git clients (QwenLM#9870)

The restricted child environment replaced the spawn environment wholesale
with a small allowlist, so a PATH-resolved git wrapper that needs a
non-allowlisted indirection variable failed every spawned command
("exec: : not found"), breaking both the unit suite and real credentialed
installs in such environments. Inherit the parent environment minus Git
behavior variables (git_*), home locations, proxies, TLS trust overrides,
loader-injection keys, and the non-GIT_* keys @simple-git/argv-parser flags
as unsafe. This keeps wrapper indirection variables flowing while ambient
Git configuration, program overrides, and network redirection still never
reach the spawned client. Adds a regression test that spawns git through a
PATH wrapper requiring a non-allowlisted variable.

* fix(core): reject ambiguous credentialed Git sources and block ambient tokens (QwenLM#9870)

* fix(core): preserve Git credential isolation

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* feat(channels): add DingTalk Workspace channel

Add a DingTalk Workspace (DWS) channel package so a workspace can be
driven from DingTalk alongside the existing channels.

- packages/channels/dws: new workspace holding the DWS client, event
  stream, environment resolution and channel implementation, with the
  event-source fixtures used by its tests.
- cli: register DWS in the channel registry and its builtin list.
- web-shell: recognise the DWS platform in the channels UI.
- docs: document the channel and its configuration under
  docs/users/features/channels.
- build/release: include the new workspace in the build, clean and
  release-version scripts and the vitest project list.

The channel watches native DingTalk todos, routes document and todo
replies back to their originating conversation, bounds notification
retries, and keeps sender identity authoritative for direct messages.

* fix(dws): classify spawn-resource errnos as not sent, and test against base's source

Round 1 review, two Critical findings.

vitest.config.ts — the new package's config was the only channel config
without the `@qwen-code/channel-base` → source alias its five siblings carry,
so `cd packages/channels/dws && npx vitest run` (the workflow AGENTS.md
prescribes) depended on a prior `tsc --build` of base. Reproduced the
reviewer's witness in this worktree with base/dist moved aside: without the
alias vitest dies in `packageEntryFailure` and runs zero tests; with it,
63/63 pass. Even when dist exists it may lag base's source — it did here, by
four days.

dws-client.ts — `DWS_NOT_SENT_ERROR_CODES` listed only the path errnos, so a
`dws` process that never started because of fd or memory exhaustion
(`EMFILE`/`ENFILE`/`ENOMEM`/`EAGAIN` and family) was classified `unknown`.
The todo and document reply paths in dws-channel.ts swallow `unknown` as
"the originating task will not be rerun", so a user's final reply was dropped
permanently on one log line instead of being retried — and the retry is safe,
since the fingerprint is not persisted when delivery fails. The set now
carries the whole `uv_spawn` pre-exec family. Everything else the callback
reports — a non-zero exit (numeric `code`), a timeout kill (`code === null`),
`ABORT_ERR`, a `maxBuffer` overrun — happened with a child already running
and stays `unknown`, because a retry there could duplicate a delivery.

The classification moved into an exported `classifyDwsCommandFailure` so the
table can be driven directly: the resource errnos need real fd or memory
exhaustion to reproduce through a spawn, which no unit test can stage safely.
The existing missing-executable test still covers the wiring end to end.

Verified: packages/channels/dws — 191 passed (5 files). Mutation-verified:
reverting the errno set turns exactly the 12 added codes red (12 failed /
51 passed); dropping the vitest alias with base/dist absent turns the suite
from 63 passed into a collection failure. eslint and prettier clean. The one
tsc error on this branch (`displayText` missing from `Envelope`) is worktree
build skew — base/dist was built 2026-08-10, base/src changed 2026-08-14, and
the field is present in the source; it reproduces identically with these
changes stashed.

* fix(dws): stop a denied sender from consuming a document comment's dedup slot

Round-2 review, R2-4 (Critical).

`notificationKey` is `documentNotificationKey(documentId, commentKey)` — no
sender in it — so a `'denied'` outcome falling into the `else` branch marked
that (document, comment) pair processed for good. Every later notification for
the same comment, live or polled, then hit
`processedMessages.includes(notificationKey)` and returned silently, including
one from a sender who IS allowed. The cursor persists, so the drop survived
restarts.

Concretely, with `senderPolicy: 'allowlist'` and `allowedUsers: ['open-bob']`:
Alice (not allowlisted) @-mentions the bot in a document comment and is denied;
Bob then mentions the bot on the same comment thread — the ordinary
multi-reviewer document flow — and is dropped forever, with no dispatch, no
pairing and no log.

A denied notification is now parked with `rememberPendingDocumentNotification`
like a `'pairing'` one rather than consuming the slot. Replay already skips a
pending entry whose sender fails `gate.isAllowed`, so a denied sender does not
get retried in; and an allowed sender reaching the same comment clears the
entry on the way through.

The existing `applies sender access policy to document mention notifications`
cannot cover this — its denied and allowed notifications are on DIFFERENT
comments, so the shared key is never exercised. New test puts both on the same
comment. Mutation-checked: restoring the old condition reddens it with
`bridge.prompt` called 0 times against an expected 1, reproducing the review's
own witness.

Verification: `npm run build` and `tsc --noEmit` clean in packages/channels/dws;
eslint clean on both changed files; full package suite 192/192 (118 in
dws-channel.test.ts, 1 new).

* fix(dws): stop a poison message, a full pending queue, and an unreachable
replay from pinning the watermark (R2-1, R2-2, R2-4 queue)

Three ways history polling could stall forever, each measured:

**R2-2, poison message.** A message whose turn threw was never marked
processed, so the watermark never advanced and every poll re-ran it as a
full agent turn — one model call per iteration, no cap, no backoff —
while the pinned watermark grew the query window without bound and the
throw starved every newer message behind it. Pending-document replay
already had retry accounting; this path had none. Inbound failures are
now counted per message and persisted in the cursor: under budget the
error still propagates (redelivery retry and the concurrent-duplicate
contract depend on that, and their tests pin it), and once the budget is
spent the message is marked processed and dropped with a logged reason.

**Pending-queue cap.** `rememberPendingDocumentNotification` threw at
MAX_PROCESSED_ITEMS, and the throw aborted the direct-message loop
before the checkpoint, the watermark and `markProcessedMessage` — so
every later poll re-scanned a growing window and re-threw on the same
never-marked message, surviving restarts in the cursor. The queue's only
drain is an allowed sender later processing the same comment, so entries
parked for unapproved senders never leave: one unpaired member
@-mentioning the bot in 5,000 distinct comments broke document history
polling until manual cursor surgery. It now evicts the oldest instead,
which costs at most a pairing prompt nobody approved.

**R2-1, the replay the fixture could not recover.** The test fake
ignored its `startTime`/`endTime`, so it certified a recovery the
production arithmetic cannot perform. Fixed on both sides: the fake now
filters by its window like the real client (and `message()` defaults
`eventTime` to now, since real messages always carry one — six fixtures
were silently relying on epoch 0), and the stale-replay guard now pulls
`notificationWatermark` back to the parked notification's event time. It
parks document notifications UNMARKED on purpose, "for polling to
recover"; on a fresh cursor the watermark started at
`connectionStartedAt` and the window opened at `watermark − 5s` —
exactly the guard's own drop boundary — so everything it parked was
strictly outside every window that watermark would ever produce.

Every fix is mutation-verified: reverting the retry budget re-runs the
poison turn once per poll (8 polls, 8 turns), restoring the queue throw
reproduces the reviewer's stderr and the pinned watermark, and dropping
the watermark pull-back leaves the replayed notification unrecovered.
Suite 194/194 green; `tsc -p packages/channels/dws` clean.

R1-2 (self-identity degradation) is not in this commit — both fixes the
review proposes collide with behaviour this suite pins deliberately; see
the thread.

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

* fix(dws): budget every inbound surface, and stop restarting a dead source (R4-1, R4-3)

R4-1: round 3 added an inbound failure budget, but wired it into one of the
three `handleInbound` call sites — the mention/live-IM path. The other two kept
the exact unbounded-retry mode the budget's own doc comment says it exists to
close.

- Document notifications (`processDocumentNotification`): a throw escapes
  `pollOnce`'s sorted loop and is swallowed by the outer catch, so nothing is
  marked processed and `notificationCheckpoint`/`notificationWatermark` — both
  assigned after the loop — never advance. Every 5s poll re-ran the same full
  agent turn, forever, starving every newer notification behind it.
- Native todos (`pollTodos`): the fingerprint is remembered only on success, so
  a todo whose turn keeps throwing was re-fetched and re-run every poll,
  forever.

`recordInboundFailure` now takes the drop action as a parameter, because "stop
re-running this" differs per surface: marking the key processed is right for a
message, a document notification carries its own `notificationKey` (and a
pending entry to clear), and a todo is re-fetched by fingerprint. The default
keeps the mention path byte-identical.

R4-3: `retryable: false` is terminal before ready — `retryLimit` returns 0 —
but `scheduleImRestart` never consulted it, and `startImSource` resets
`restartAttempts` to 0 every time a subscription becomes ready. The backoff
exponent therefore stayed at 0, so a permanently denied consumer (permission
revoked, subscription not allowed) was respawned at a constant ~3s forever —
one `dws event consume` child every 2-3s per affected source — while the
channel reported itself connected and delivered nothing for that source.
Post-ready now matches pre-ready: terminal, with a log line saying so.

Verification (`cd packages/channels/dws`):
- `npx vitest run` — 197 passed (was 194; three new tests).
- `npx tsc -p tsconfig.json --noEmit` — clean.
- Mutation checks, one per fix, each turning exactly its own test red and
  leaving the other 122 green:
  - drop the `retryable === false` guard -> `stops restarting a source that
    died permanently after becoming ready` fails.
  - drop the document-path budget -> `drops a document notification whose turn
    keeps failing, and stops starving newer ones` fails (the newer
    notification is never reached).
  - drop the todo-path budget -> `drops a native todo whose turn keeps
    failing` fails (8 turns instead of 5).
- eslint + prettier clean.

Not addressed in this commit: R4-2 (checkpoint drain overwriting the stale
replay pull-back), R4-4, R1-2, and R4-5..R4-8.

* fix(dws): stop an in-flight poll from clobbering the stale-replay pullback (R4-4)

`handleImMessage` leaves a replayed document notification UNMARKED on purpose,
for history polling to pick up, and pulls `notificationWatermark` back to the
replay's `eventTime` so a future window can reach it. `pollOnce` then wrote
`checkpoint.endTime` over that watermark unconditionally when its own window
finished — and `checkpoint.endTime` is always past the replay's `eventTime`.

The race is not hairline: `runLoop` polls immediately on connect and the IM
subscriptions start before the poll loop, so a startup replay arrives precisely
while poll #1's `listDirectMessages` is awaiting. One clobber puts the parked
replay outside every window the watermark will ever produce — no turn, no log,
no error, and it survives restarts because `saveCursor()` persists it.

`pollOnce` now records whether the watermark was pulled back while its
direct-message fetch was in flight, and on that path drops the window instead of
finishing it: neither the advance nor the paginated checkpoint resume is safe,
because the checkpoint was itself derived from the pre-pullback watermark. The
next poll re-derives a window from the pulled-back value.

Test: `keeps the stale-replay pullback when a poll was already in flight` emits
the replay from inside `listDirectMessages`. Mutation-checked — forcing the
guard false reddens it with `inbound` empty, matching the reviewer's witness
(`dispatched = 0`). It also asserts the second query window opens at or before
the replay's `eventTime`, so a fake that ignored its window could not certify it.

* fix(dws): stop three silent, permanent losses of a document mention (R6-1/R6-2/R6-3)

All three Criticals round 6 raised share a failure shape: a document comment is
consumed by something that had no right to consume it, the user gets no reply,
and nothing is logged. Each is fixed at the point that consumes the slot.

R6-1 — `handleImMessage` pullback (dws-channel.ts): R4-4 rescued a stale replay
by pulling the notification watermark back, but the flag `pollOnce` consults is
cleared at the top of every fetch, so it only ever covered a replay that landed
DURING one. A pullback arriving in the gap between two polls is reset before it
is read; a persisted multi-page `notificationCheckpoint` then resumes a window
that starts after the replay and finishes by writing `checkpoint.endTime` back
over the pulled-back watermark. The replay was left unmarked on purpose, so
after that no window ever reaches it again. The pullback branch now drops the
checkpoint as well, which makes the rescue durable regardless of when the
replay arrived; the in-flight flag still guards the during-a-fetch case.

R6-2 — in-flight awaiter (dws-channel.ts): a pending entry means the in-flight
turn PARKED the comment for a sender it would not serve, which says nothing
about the caller waiting behind it. Marking unconditionally consumed an ALLOWED
sender's mention outright — replay only re-drives a parked entry whose own
`senderId` passes the gate (the denied one never will), and the allowed
sender's marked message key is skipped by every later history poll. The awaiter
now marks only when the comment is genuinely processed, or when this caller is
no more entitled to it than the sender already parked. This is what the
denied-sender comment further down already claimed happened ("an allowed sender
reaching the same comment clears the entry on the way through") — the awaiter
was the path that never let them reach it.

R6-3 — failure-budget drop closure (dws-channel.ts): the closure marked the
sender-agnostic `notificationKey` (`document\0comment`, no sender), so five
failed turns — about 25s of transient model or bridge trouble, since each 5s
poll re-runs an unmarked notification — dropped every FUTURE mention of that
comment from anyone, permanently and across restarts. It now marks only the
failing message's own `key`, which is what stops the window re-running it, so
the R4-1 starvation this budget closes stays closed.

Tests (dws-channel.test.ts), each mutation-verified against the pre-fix code:
- `keeps a stale-replay pullback that arrives between two polls` — persists a
  bounded checkpoint, emits the replay with no poll in flight, asserts the
  checkpoint is released and the next window reaches back over the replay.
  Reverting R6-1: `expected { startTime: … } to be undefined`.
- `lets an allowed sender through while a denied turn on the same comment is in
  flight` — the concurrent counterpart to the existing R2-4 test, which lets
  the denied turn finish first and so cannot reach the awaiter. Reverting R6-2:
  the allowed sender's prompt is never called.
- `lets a later mention of a dropped comment retry with a fresh budget` — five
  failing polls, then a different reviewer on the same comment after the
  outage. Reverting R6-3: `expected [] to deeply equal [ ObjectContaining{…} ]`.

Verification: `npx vitest run` in packages/channels/dws — 201 passed (5 files);
`npx tsc --noEmit -p packages/channels/dws/tsconfig.json` clean; `npm run build`
in that package clean; eslint and prettier clean on both changed files.

R1-2 is untouched: it still needs a maintainer call on which pinned contract
gives, and is not something this commit should decide.

* fix(dws): resolve the sender gate before reading a mentioned document (R7-1)

`parseDocumentMentionNotification` reconstructs `(documentId, commentKey)`
from rendered message text, so a bare alidocs URL in an ordinary DM forges a
mention card the channel cannot tell apart from a genuine platform
notification. `processDocumentNotification` then called
`readDocumentContext` on that attacker-named document BEFORE `handleInbound`
resolved the sender gate, so under the documented default
`senderPolicy: 'pairing'` an unpaired stranger could force this profile to
perform an authenticated read of any document it can reach — a turn the
channel would never serve them.

Resolve `gate.isAllowed(message.senderId)` first and read only for a sender
this channel will actually answer. The envelope already carries a "Document
Markdown was unavailable" fallback, the `preflightInbound` document branch
still parks the mention exactly as before, and
`replayPendingDocumentNotifications` re-enters this path once the sender is
approved, so an approved turn still gets its document context — just after
the gate instead of before it.

BEHAVIOR FLIP: `replays a pairing-pending document mention after approval`
pinned `readDocument` being called once for the still-unpaired sender and
twice overall. That pinned expectation was the defect: it asserted an
authenticated read driven by a sender the gate had already refused. It now
expects zero reads before approval and one after. Verified by mutation —
reverting the guard turns both this test and the new forged-mention test red.

Still open on this class and NOT addressed here: the pairing-code write into
the attacker-named comment thread. Closing that needs either fail-closed
verification that `commentKey` is a real comment on `documentId` mentioning
this profile (no DWS CLI surface exposes it — `listMentionedMessages` covers
group IM, not document comments) or structured mention events, so it is a
maintainer contract call rather than a local fix.

Verification:
- packages/channels/dws: 202 passed (5 files), including the new
  `does not read a forged document mention before the sender gate resolves`
- tsc --noEmit -p packages/channels/dws/tsconfig.json: clean
- eslint + prettier --check on both changed files: clean

* fix(dws): list the dws channel as a cli test build prerequisite

`channel-registry.ts` dynamically imports `@qwen-code/channel-dws`, whose
package.json resolves the bare specifier to `dist/index.js` and which
`packages/cli/vitest.config.ts` does not alias to source. It therefore
belongs in `DIST_PREREQUISITES['packages/cli']` alongside every other
builtin channel, so a cli test run on an unbuilt checkout reports the
actionable "run npm run build" message instead of a raw resolution error.

This is what the required `Test (ubuntu-latest, Node 22.x)` check caught
on 4bf0407: scripts/tests/vitest-global-setup.test.js asserts the list
stays in sync with the registry, and dws was the one registry import
missing from it.

Verified: `npx vitest run scripts/tests/vitest-global-setup.test.js`
29 passed; reverting this one line reproduces the CI assertion exactly
("missing prerequisite entry for packages/channels/dws"), 1 failed | 28
passed. prettier --check and eslint clean.

* fix(dws): close current review blockers

* test(dws): pin fail-closed self identity gate

* fix(dws): preserve retryable inbound work

* fix(dws): preserve in-flight catch-up mentions

* fix(dws): align channel-base on the workspace version so npm ci resolves

`Dependency CVE audit` has failed every run with:

    npm ci can only install packages when your package.json and
    package-lock.json are in sync.
    Missing: @qwen-code/channel-base@0.21.11 from lock file

The diagnosis of "stale base" was right, but the stale file is this PR's
own. `packages/channels/dws` was written when the workspace was at
0.21.11 and pins that version; every sibling channel — dingtalk, feishu,
github, gitlab, qqbot, telegram, wecom, weixin — now says 0.21.14, which
is what `packages/channels/base` actually publishes. A workspace package
cannot satisfy 0.21.11, so npm resolved `@qwen-code/channel-base` for dws
from the REGISTRY instead of linking the sibling, leaving a nested
`packages/channels/dws/node_modules/@qwen-code/channel-base` entry that
`npm ci` refuses. Merging current main cannot fix it: main is not where
the pin lives.

Bump dws to 0.21.14 for both its own version and its channel-base
dependency, matching every sibling, and regenerate the lockfile. The
nested registry entry is gone and dws now links the workspace like the
others. `npm ci --dry-run` completes, and dws typechecks and passes all
211 tests against the workspace channel-base rather than the published
0.21.11 it was resolving before.

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

* fix(dws): replay failed direct messages, and close open review items

* fix(deps): bump tar to 7.5.22 to unblock CVE audit (QwenLM#9394)

The 2026-08-21 advisory GHSA-r292-9mhp-454m flags tar <= 7.5.20 as
high severity, failing the Dependency CVE audit gate. Main already
moved to 7.5.22 in QwenLM#9703, but that landed after this branch's last
merge of main. Bump the lockfile entry in-range (core/cli declare
^7.5.19) to match main, and regenerate the committed NOTICES.txt
artifact whose freshness is enforced by CI.

* fix(dws): unblock npm ci, add publish metadata, and keep todo fetch failures out of the turn budget (R13-1, R13-2, R14-1)

* fix(dws): dedup threaded pairing comments on a persisted marker instead of the rotating code (R15-1)

* fix(dws): clear the todo pairing marker when pairing resolves, not on turn success (R16-1)

* fix(dws): address current review blockers

* fix(dws): satisfy event fixture lint

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
* fix(session): preserve source titles when branching

Forked sessions could fall back to a UUID fragment when the source
name came from its first prompt rather than a custom title.

- Forward the active picker name through Web Shell branch requests
- Resolve prompt-backed display names and include them in collision scans
- Allocate the first free numeric suffix and normalize nested forks
- Preserve explicit side-task names and add regression coverage

* fix(session): align branch title behavior across clients

Review found that CLI and ACP still derived branch names differently,
while the Web Shell smoke test asserted the previous request body.

- Preserve explicit names while normalizing only derived title suffixes
- Reuse Core display-name and title helpers across branch entry points
- Cover legacy, fallback, and isolation paths and update the smoke E2E

* fix(session): harden branch title fallbacks

Round-two review found empty normalized titles could bypass fallbacks,
while client-echoed names made explicit-name semantics ambiguous.

- Treat empty derived titles as absent across branch entry points
- Stop Web Shell name echoing and align CLI fallback with the picker
- Pin missing-session, invalid-id, and bounded-scan behavior

* fix(session): pin branch title normalization and document fallback divergence

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

* fix(session): fall back on empty branch-title bases

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
Co-authored-by: qwen-code-autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
…enLM#9844)

* fix(telemetry): restore usage aggregate when a session swap fails

A /resume or /branch that fails after GeminiClient.initialize() replayed
the incoming session's stored telemetry leaves that whole replay in the
process-wide usage aggregate, and the /branch rollback's own
re-initialize() adds a second copy of the parent's history on top —
persistSessionUsage then writes the surviving session out inflated for
the life of the process (QwenLM#9833).

Make the replay undoable with an explicit per-swap transaction:

- UiTelemetryService.snapshotForReplay / restoreFromReplaySnapshot
  capture and put back the process-wide metrics, the incoming and
  outgoing session buckets, the closed-session flags and both cached
  token counts. Restore overwrites rather than subtracts, so it stays
  correct after the rollback's own replay landed on top.
- GeminiClient.beginTelemetrySwap / commitTelemetrySwap /
  abortTelemetrySwap own the snapshot for exactly one swap. The snapshot
  is armed inside initialize()'s replay branches — keyed off the
  client-private initializedSessionId that actually decides whether a
  replay happens, never a caller's config-id guess — so replays outside
  a transaction (process startup, ACP) arm nothing and cannot be
  restored by a later failed swap.
- abort forgets initializedSessionId when it still names the abandoned
  session, so a retry replays again instead of early-returning into a
  permanently under-counted session.
- The /resume and /branch hooks open the transaction before the core
  swap, commit it at the UI re-key (moving uiSwapped up to that point:
  a failure after the stats provider re-keyed must not roll core back or
  drop the bucket the display now reads), and abort it on the rollback
  path after core is restored.

Usage accounting only: Goal state, the recorder's on-disk transcript and
what a session resumes with are untouched.

* fix(telemetry): reject concurrent session swaps to protect the swap slot

A second /resume or /branch started while the first is still in flight
entangles the single telemetry-swap slot: the second beginTelemetrySwap
was a ??= no-op while both replays mutated the same aggregate, so the
first swap's stale settlement either no-oped or restored its snapshot
over the second swap's committed state — permanently double-counted
usage or a zeroed re-keyed display with core rolled back underneath.
Nothing else serializes the swaps: the session picker fires them
fire-and-forget and no input gate covers them.

beginTelemetrySwap now returns false when a transaction is already open
(the slot doubles as the session-switch latch); both hooks reject the
concurrent attempt with a visible error and never settle a slot they do
not own. Pins the review's SCENARIO A interleaving as a regression test.

* fix(telemetry): capture the outgoing session when the swap transaction opens

The replay snapshot's outgoing session was keyed on initializedSessionId
at arm time, but abortTelemetrySwap clears that field after a failed swap
(it names the abandoned session). The NEXT swap then armed with
outgoing = undefined, so its snapshot never captured the live session's
bucket — and the rollback's re-initialize wiped its never-persisted state
(skill invocations, live events) for the life of the process.

beginTelemetrySwap now stores outgoingHint from the config session id
(the hooks call it before startNewSession, so it still names the session
the process is on); armTelemetrySwapUndo prefers the hint and falls back
to initializedSessionId. Doc comments updated to match.

* fix(telemetry): log the session-swap transaction lifecycle

The swap transaction ran with zero logging, so a recurrence of the
double-count class after a flaky /resume could not be diagnosed without
reproducing the exact failure ordering — there was no record of whether
the transaction armed, whether abort applied a restore or no-op'd unarmed,
or which sessions were involved.

Add [TELEMETRY_SWAP_*] debug lines for begin (including latch rejection),
arm, commit, and abort, matching the file's existing [RESET_CHAT_*]
guarded-debug convention.

* fix(telemetry): engage the swap latch before touching the outgoing session

The session-switch latch (beginTelemetrySwap) opened only at the core-swap
step, after the outgoing session id was captured and the pre-swap work
(finalize/snapshot/fork/title persistence for /branch, loadSession for
/resume) had run. Two defects on concurrent fire-and-forget swaps (QwenLM#9844
review):

- A pre-latch failure reached the catch's settle branch with no ownership
  check: commitTelemetrySwap clears the shared single slot unconditionally,
  so it settled whatever transaction the slot held — possibly a DIFFERENT
  in-flight swap — discarding its armed undo and reintroducing the QwenLM#9833
  double-count when that swap later failed.
- A concurrent swap that rolled back during the pre-latch window freed the
  slot, and the stale outgoing-session id captured before the window made
  the fork/rollback land on a session the UI never shows.

Move the latch open to the top of both hooks, recapture the outgoing
session under it, and guard both catch settle branches so an attempt only
ever settles the transaction it opened itself (swapOpened). The /resume
missing-session early return now settles its own opened transaction so the
slot is never left stuck. The duplicated multi-line transaction rationale
comments collapse into cross-references to the lifetime contract's single
home (beginTelemetrySwap's JSDoc in core client.ts).

* test(telemetry): guard the fork-bucket cleanup and the branch latch rejection

- The failed-/branch test asserted the fork's replay bucket cleanup on
  SESSION_B, which never receives an event there: getMetricsForSession
  returns fresh empty metrics for unknown ids, so the assertion passed
  unconditionally. Assert on the fork's actual derived id — probe-verified
  to flip red when the incoming-bucket delete is skipped (QwenLM#9844 review).
- Add the /branch latch-rejection coverage the hook never had: a /branch
  submitted while a /resume swap is in flight is rejected at the latch
  with no fork created and the in-flight transaction untouched, and the
  in-flight swap's later failure still restores exactly. A hook-level
  unit test pins that a rejection settles nothing (no commit/abort).
- Pin the latch ordering for /resume (rejection happens before the
  incoming loadSession) and that resuming a missing session settles the
  transaction it opened, leaving the single slot free for the next swap.

* fix(telemetry): drop the inert swapRejected guard from the branch settle path

swapRejected was set true only on the line immediately before the step-0
throw, which runs before swapOpened = true — so the catch's settle guard
`!swapRejected && swapOpened` was provably equivalent to `swapOpened`
alone (the shape the sibling useResumeCommand catch already uses). Remove
the dead flag and correct the comments that misattributed the invariant
to it: the statement ordering (throw before `swapOpened = true`) is what
keeps a latch-rejected attempt from settling the in-flight swap's slot.

Suggested in the QwenLM#9844 review.

* test(telemetry): pin the swap-slot settle on failed /resume and /branch paths

The catch-block settle (`else if (swapOpened) commitTelemetrySwap()`) had
no coverage in either hook, and the fork-bucket regression guard derived
its session id through a non-null assertion that could pass vacuously:

- /resume: add tests for both settle subpaths — a pre-core-swap failure
  (session load rejects) and a post-UI-commit failure (loadHistory throws
  after the re-key) — each asserting the catch committed (never aborted)
  the transaction and that the next swap is admitted instead of rejected
  with "already in progress". Deleting the settle block fails both.
- /branch: the existing failure tests use a client mock without
  commitTelemetrySwap, so the settle was an optional-chained no-op there.
  Add a forkSession-rejects test against a stateful swap-slot fake that
  observes the slot release as the next begin being accepted.
- Harden the fork-bucket guard to require the fork key to actually exist
  in the session store before asserting on it: getMetricsForSession
  returns fresh empty metrics for unknown ids, so a missing forkId
  (e.g. once removeSession becomes realistic) passed vacuously. Making
  the fake removeSession delete the fork now fails the test loudly.

Suggested in the QwenLM#9844 review.

* fix(telemetry): keep the live parent initialized when the branch rollback armed the undo

abortTelemetrySwap assumed swap.undo.sessionId always names the abandoned
incoming session, but when /branch fails between startNewSession(fork) and
initialize() (waitForGoalRuntime rethrows), the forward replay never runs
and the rollback's own re-initialize arms the undo with the live parent's
id. The blind clear then forgets a correctly-initialized session, so the
next same-session initialize re-replays stored telemetry on top of the
live aggregate — a permanent double-count. Guard the clear on the undo
actually belonging to the incoming session (QwenLM#9844 review).

* test(telemetry): derive the fork id pre-cleanup and make removeSession realistic

The fork-bucket anti-vacuity guard derived the fork id from post-cleanup
store membership, asserting the fork survives a failed /branch — the exact
opposite of production, which calls removeSession(newSessionId) in that
same failure branch. Make the fake removeSession delete the entry like the
real SessionService, return the shared session-service object from
makeFakeEnv, and derive the fork id from forkSession's call args instead.
The bucket assertion stays non-vacuous without requiring the fork to
outlive the cleanup the production path performs (QwenLM#9844 review).

* test(telemetry): share the swap-slot client fake and model the abort boolean

The stateful makeSwapSlotClient fake — the only place the hook tests model
the client's single-slot begin/commit/abort contract — existed twice,
byte-for-byte identical, in useResumeCommand.test.ts and
useBranchCommand.test.ts, and under-modelled abortTelemetrySwap(): boolean
(both copies returned nothing). Extract it to a shared test-utils module
and model the boolean return — true when the abort settled an open
transaction, false when nothing was open — so 'abort ran but restored
nothing' is observable. The two core-swap rollback tests now observe the
abort through it (QwenLM#9844 review).

* fix(telemetry): re-initialize the outgoing session when a failed /resume rolls back

Mirror /branch's rollback in useResumeCommand: after startNewSession(oldSessionId, prevSessionData) re-run client.initialize() on a best-effort reload of the outgoing session's persisted state, so initializedSessionId names the live session again. Without it a subsequent same-session /resume skips initialize()'s early return, and its replay wipes the live bucket (skill invocations are never persisted) while re-adding stored telemetry on top of the aggregate that already contains it — the QwenLM#9833 double-count class reintroduced by failed /resume -> same-session /resume (QwenLM#9844 review).

Also make the shared swap-slot fake honest about abortTelemetrySwap's boolean: it over-approximates the open-but-unarmed case (real client returns false there), so the malformed-Goal test no longer asserts the return value and the fake's JSDoc says what it models. Remaining true-return assertions sit only on shapes where initialize() ran during the open transaction, which arms the real undo.
…not streamState (QwenLM#9971)

handleCronTextChunk used streamState.has(sessionId) to tell prompt-response
chunks from cron/non-prompt chunks. That discriminator fails in both
directions (QwenLM#6094 items 1 and 2):

- With blockStreaming: 'on', onResponseChunk early-returns and never
  populates streamState, so during an active cron flow every prompt-response
  chunk leaked into cronBuffer and was re-sent by the 2s idle flush on top of
  the BlockStreamer delivery (duplicate messages).
- A residual streamState entry from a finished turn whose flush has not
  settled (e.g. cancelled/errored prompt) kept the guard true, silently
  dropping all subsequent cron textChunks for that session.

Track sessions with a prompt turn in flight in activePromptSessions,
maintained by the onPromptStart/onPromptEnd hooks. ChannelBase always pairs
the two per turn (onPromptEnd runs in the prompt path's finally, even on
error/cancel), and the marker is independent of streaming config, so it is a
reliable discriminator. The set is also cleared in disconnect() and
onSessionDied alongside the existing streaming-state cleanup.

Items 3 and 4 from the issue are already addressed on main (plain-text
fallback for no-msgId sends; setBridge re-attaches _cronTextHandler). Items
5 and 6 remain open as separate low-priority work.
…9568)

* fix(cli): mark estimated compression token counts in banners

/compress-fast anchors its banner on the API-reported prompt count
(system prompt + tools + history) minus a locally estimated reduction,
while a later /compress re-estimates history-only once the stored count
is estimate-derived. Consecutive banners therefore show numbers on
different scales (e.g. 170k -> 75.8k followed by 26.6k -> 14.5k), which
reads as ~50k of context silently lost even though nothing was dropped.

Expose per-side token-count provenance on ChatCompressionInfo
(originalTokenCountIsEstimated, mirroring newTokenCountIsEstimated) from
both tryCompress and compressFast, plumb it through the compression
history item, and render estimated figures with a '~' prefix in the
CompressionMessage banner, the auto-compaction notice, and the
headless/ACP messages. Display-only: no compression behavior, thresholds,
or recorded counts change.

Fixes QwenLM#9309

* fix(cli): mark estimated counts in the ACP auto-compaction notice

The ACP session auto-compaction diagnostic interpolated raw token counts and ignored the QwenLM#9309 provenance flags, so estimated figures reached ACP clients unmarked. Route both counts through the same '~'-prefix formatting as the TUI notice and cover it with an asymmetric-flag regression test.

Also harden the marker tests per review: asymmetric estimation flags in compressCommand/useGeminiStream fixtures so a swapped flag-argument mutation is detectable, an executionMode:'acp' case for the /compress stream_messages branch, and a null-event ChatCompressed case pinning the 'unknown' fallback.

* fix(core): derive compression provenance from the selected count source

The estimated flag published on compression banners was computed from the
stored count's provenance or the mere presence of an override, not from the
count source selected for the attempt:

- Auto-compaction / hard-tier rescue: a previously API-reported stored count
  forced the flag false even though the attempt publishes the precomputed
  effective count, which always folds in locally estimated parts (pending
  user message, previous output).
- Reactive overflow: a limit/config/default fallback passed as the override
  forced the flag false whenever the provider did not report actualTokens.

Both paths omitted the `~` marker and presented projected or fallback values
as API-reported counts (review on QwenLM#9568). Derive the flag from the selected
source: a caller-precomputed effective count is always estimated, and the
override now carries explicit provenance ({ count, isEstimated }) so only a
provider-reported actualTokens override marks the count authoritative.

* test(core): pin send-path effective token counts exactly (QwenLM#9309)
…wenLM#9758)

* fix(core): emit OpenRouter's reasoning disable when thinking is off

The AUTO-mode permission classifier's stage-1 side query forces a
respond_in_schema tool call (tool_choice: 'required') with a 256-token
budget and includeThoughts: false. On OpenRouter endpoints the
thinking-disable rendered only into shapes the gateway ignores
(chat_template_kwargs.enable_thinking for qwen-family models), and the
pipeline's unconditional strip then removed the `reasoning` object —
OpenRouter's native thinking knob. Thinking stayed on, the model spent
the whole budget on reasoning, never emitted the tool call, and the
classifier fail-closed with "Classifier stage 1 unavailable" (QwenLM#9757).

Mirror the isDeepSeekHostname precedent: hostname-gated detection
(openrouter.ai / *.openrouter.ai) and emit `reasoning: { enabled: false }`
in the reasoningDisabled branch after the strip — the provider
buildRequest hook runs before the strip, so emitting earlier would be
removed again. Applied endpoint-wide rather than qwen-family-gated:
`reasoning` is an OpenRouter provider-level parameter, unlike
`enable_thinking`, which is a qwen wire field that leaks upstream on
non-qwen routings. thinkingMandatory models stay exempt; DashScope
(both shapes), vLLM/SGLang, DeepSeek hostname, and the official OpenAI
endpoint are untouched.

Repro + regression coverage added in pipeline.test.ts (red before the
fix, green after).

* test(core): cover OpenRouter reasoning guard
…ve late attach (QwenLM#9824)

* fix(core): validate skill commands against the live provider to survive late attach

SkillTool caches the model-invocable command set during construction,
but in interactive mode the provider is only registered after
CommandService initialisation resolves — after Config.initialize() has
already warmed the tool registry. The cache then stays empty until an
unrelated SkillManager change event re-runs refreshSkills(), so
validateToolParams intermittently rejects commands announced by the
per-turn skills drain (issue QwenLM#9821).

Consult the synchronous provider live in validateToolParams instead,
applying the same file-based-skill name shadowing as
collectAvailableSkillEntries so path-gated skills stay gated. The
execute path already reads the executor live. Regression tests cover
late attach, paths gating, and the no-provider SDK path.

* fix(core): keep hidden skills from shadowing commands

* fix(core): default hidden skill collection in tests

* fix(core): record hidden skill fallback failures

* test(core): cover hidden skill fallback args

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qqqys
qqqys force-pushed the simplify/enum-selector branch from 8473eb4 to db1b9cc Compare August 25, 2026 08:20
@qqqys

qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Owner Author

Opened in the wrong repo by mistake — this change belongs upstream: QwenLM#9997. Closing.

@qqqys qqqys closed this Aug 25, 2026
@github-actions

Copy link
Copy Markdown

Code Coverage Summary

Package Lines Statements Functions Branches
CLI N/A% N/A% N/A% N/A%
Core 88.59% 88.59% 90.31% 87.04%
CLI Package - Full Text Report
CLI full-text-summary.txt not found at: coverage_artifact/cli/coverage/full-text-summary.txt
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   88.59 |    87.04 |   90.31 |   88.59 |                   
 src               |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/__mocks__/fs  |       0 |        0 |       0 |       0 |                   
  promises.ts      |       0 |        0 |       0 |       0 | 1-48              
 src/agents        |   90.24 |    84.51 |   94.55 |   90.24 |                   
  ...transcript.ts |   88.49 |    84.09 |     100 |   88.49 | ...32,640,646-650 
  ...ent-resume.ts |   85.64 |       78 |    85.1 |   85.64 | ...1793-1797,1800 
  ...ound-tasks.ts |   95.19 |    90.75 |   96.42 |   95.19 | ...1889,1897-1898 
  forkedAgent.ts   |   93.18 |    83.47 |   94.44 |   93.18 | ...90,698,703-710 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |   95.27 |    88.23 |   98.33 |   95.27 | ...1478,1492-1494 
  ...w-snapshot.ts |   75.73 |    72.22 |    87.5 |   75.73 | ...21,445,452-454 
  worktree-pin.ts  |     100 |    88.23 |     100 |     100 | 78,99             
 src/agents/arena  |   77.39 |    69.23 |   78.94 |   77.39 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   76.45 |    66.26 |   78.57 |   76.45 | ...1888,1894-1895 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    73.46 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   78.07 |    85.19 |   76.12 |   78.07 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |   90.87 |    85.24 |   93.18 |   90.87 | ...83,685,687-688 
  TmuxBackend.ts   |    90.7 |    76.55 |   97.36 |    90.7 | ...87,697,743-747 
  detect.ts        |   31.25 |      100 |       0 |   31.25 | 34-88             
  index.ts         |     100 |      100 |     100 |     100 |                   
  iterm-it2.ts     |     100 |     92.1 |     100 |     100 | 37-38,106         
  tmux-commands.ts |    6.64 |      100 |    3.03 |    6.64 | ...93-363,386-503 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...agents/runtime |   93.32 |    87.44 |   91.34 |   93.32 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   90.27 |    80.44 |   80.95 |   90.27 | ...2525,2571-2573 
  agent-events.ts  |     100 |      100 |     100 |     100 |                   
  ...t-headless.ts |   93.49 |    89.41 |   83.33 |   93.49 | ...96-497,500-501 
  ...nteractive.ts |   81.01 |    82.35 |   76.66 |   81.01 | ...33,535-538,541 
  ...statistics.ts |   98.29 |    82.55 |     100 |   98.29 | 141,165,206,239   
  agent-types.ts   |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ool-policy.ts |   98.38 |      100 |    92.3 |   98.38 | 85-86             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...-scheduler.ts |   97.43 |    96.36 |     100 |   97.43 | 128-130           
  ...ow-journal.ts |   92.78 |    78.12 |     100 |   92.78 | ...49-150,192-194 
  ...ta-literal.ts |   95.96 |    92.68 |     100 |   95.96 | ...78-379,395-396 
  ...chestrator.ts |   93.87 |    90.47 |   91.48 |   93.87 | ...2216,2309-2312 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...low-runner.ts |   95.77 |    84.16 |      95 |   95.77 | ...88,356,376-379 
  ...ow-sandbox.ts |   97.29 |    88.84 |     100 |   97.29 | ...1835,1841-1842 
  ...flow-saved.ts |   96.51 |    94.36 |     100 |   96.51 | 134-135,234-237   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 170-171,270       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   84.25 |    85.18 |   91.03 |   84.25 |                   
  TeamManager.ts   |   77.21 |    83.04 |   83.87 |   77.21 | ...1832,1855-1856 
  identity.ts      |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...sionBridge.ts |     100 |      100 |     100 |     100 |                   
  mailbox.ts       |   96.02 |    87.23 |     100 |   96.02 | 352-358           
  ...ptAddendum.ts |     100 |      100 |     100 |     100 |                   
  tasks.ts         |   89.29 |       83 |     100 |   89.29 | ...1000,1044-1045 
  team-events.ts   |   73.68 |      100 |   66.66 |   73.68 | 140-144,151-155   
  teamHelpers.ts   |   91.71 |    94.44 |      95 |   91.71 | ...18-319,355-365 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   95.06 |    95.16 |   98.21 |   95.06 |                   
  ...on-harness.ts |   96.49 |       85 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |     100 |    96.77 |     100 |     100 | 158,167           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   85.58 |    87.79 |   77.53 |   85.58 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   84.12 |     87.1 |   75.28 |   84.12 | ...9001,9008-9009 
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  models.ts        |     100 |      100 |     100 |     100 |                   
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  storage.ts       |   96.02 |    93.38 |   89.28 |   96.02 | ...18-719,722-723 
 ...nfirmation-bus |   98.27 |    97.22 |     100 |   98.27 |                   
  message-bus.ts   |   98.14 |    97.14 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   92.66 |    88.53 |   93.83 |   92.66 |                   
  ...on-restore.ts |   88.23 |    85.41 |     100 |   88.23 | ...60,63-64,67-68 
  baseLlmClient.ts |    88.4 |    83.68 |   81.81 |    88.4 | ...59,672,678-680 
  client.ts        |   92.24 |    88.08 |   91.48 |   92.24 | ...4522,4620-4621 
  ...tGenerator.ts |   87.45 |    88.09 |   88.88 |   87.45 | ...08-509,554-560 
  ...lScheduler.ts |   89.82 |    84.81 |   94.73 |   89.82 | ...6483,6511-6527 
  ...entContext.ts |   96.63 |    90.13 |   96.66 |   96.63 | ...42,444-445,512 
  geminiChat.ts    |   95.18 |    90.76 |   96.69 |   95.18 | ...5700,5745-5746 
  geminiRequest.ts |     100 |      100 |     100 |     100 |                   
  genai-compat.ts  |     100 |      100 |     100 |     100 |                   
  ...MediaLimit.ts |     100 |       96 |     100 |     100 | 96                
  ...htProtocol.ts |    9.09 |      100 |       0 |    9.09 | ...9,62-66,69-110 
  ...ream-error.ts |     100 |      100 |     100 |     100 |                   
  logger.ts        |   87.41 |    87.02 |     100 |   87.41 | ...64-568,614-628 
  ...lay-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...dispatcher.ts |     100 |      100 |     100 |     100 |                   
  ...tyDefaults.ts |     100 |      100 |     100 |     100 |                   
  ...olExecutor.ts |   93.54 |    83.33 |      50 |   93.54 | 46-47             
  output-styles.ts |     100 |      100 |     100 |     100 |                   
  ...on-helpers.ts |   93.49 |    78.57 |     100 |   93.49 | ...10-211,228-229 
  ...issionFlow.ts |   98.98 |    96.96 |     100 |   98.98 | 109               
  ...try-policy.ts |     100 |      100 |     100 |     100 |                   
  ...ell-policy.ts |   94.89 |    88.54 |     100 |   94.89 | ...51-252,297-298 
  prompts.ts       |   93.89 |    91.66 |      85 |   93.89 | ...1272,1475-1476 
  ...ing-effort.ts |     100 |      100 |     100 |     100 |                   
  ...n-recovery.ts |   95.13 |       80 |     100 |   95.13 | ...06-107,142-144 
  ...t-profiler.ts |    97.9 |    81.15 |   88.23 |    97.9 | 117,124-125,130   
  ...port-retry.ts |     100 |      100 |     100 |     100 |                   
  tokenLimits.ts   |     100 |     92.1 |     100 |     100 | 87,122-139        
  ...-arguments.ts |     100 |      100 |     100 |     100 |                   
  ...reparation.ts |     100 |      100 |     100 |     100 |                   
  ...tion-guard.ts |   90.38 |    94.73 |     100 |   90.38 | 83-87             
  ...allIdUtils.ts |   98.81 |    91.22 |     100 |   98.81 | 43,52             
  ...okTriggers.ts |   99.45 |    92.43 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |     92.3 |     100 |     100 | 86,104            
  turn.ts          |   99.19 |    94.48 |     100 |   99.19 | 707-708,777       
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   96.59 |    89.11 |   97.43 |   96.59 |                   
  ...tGenerator.ts |   97.67 |    88.91 |   97.43 |   97.67 | ...1497,1526,1537 
  converter.ts     |   96.19 |    89.25 |     100 |   96.19 | ...1334,1555-1557 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   89.24 |    72.72 |   94.11 |   89.24 |                   
  ...tGenerator.ts |   87.54 |    71.42 |   93.75 |   87.54 | ...93-294,356-362 
  index.ts         |     100 |    85.71 |     100 |     100 | 51                
 ...ntentGenerator |   96.65 |     91.3 |   95.23 |   96.65 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   96.59 |    90.75 |      95 |   96.59 | ...1299-1300,1328 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |    92.2 |    90.85 |   96.58 |    92.2 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   91.26 |    89.66 |   96.87 |   91.26 | ...1948,2117-2132 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   68.25 |    82.35 |      50 |   68.25 | 44-53,74-78,90-94 
  ...tGenerator.ts |      70 |    73.33 |     100 |      70 | ...07-112,121-127 
  pipeline.ts      |   95.39 |    91.56 |     100 |   95.39 | ...1458-1459,1566 
  ...ix-caching.ts |   95.23 |    92.85 |     100 |   95.23 | 45-46,69-70       
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   92.11 |    92.25 |     100 |   92.11 | ...21-522,542-545 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   97.23 |    91.92 |   98.64 |   97.23 |                   
  dashscope.ts     |   98.42 |    95.27 |   96.55 |   98.42 | ...51-752,894-895 
  deepseek.ts      |   95.23 |    89.79 |     100 |   95.23 | ...49-150,163-164 
  default.ts       |   98.87 |       96 |     100 |   98.87 | 178,304           
  index.ts         |     100 |      100 |     100 |     100 |                   
  mimo.ts          |   94.11 |    66.66 |     100 |   94.11 | 29,52-53          
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  mistral.ts       |   96.07 |    73.33 |     100 |   96.07 | 32-33             
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |      90 |    76.31 |     100 |      90 | ...,72-73,173-175 
 src/extension     |   88.79 |    86.19 |   93.46 |   88.79 |                   
  ...ive-safety.ts |   97.77 |    93.75 |     100 |   97.77 | 100-101           
  ...-converter.ts |   80.55 |    73.66 |     100 |   80.55 | ...1133,1179-1180 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |     100 |      100 |     100 |     100 |                   
  ...git-client.ts |     100 |      100 |     100 |     100 |                   
  ...redentials.ts |   95.33 |    89.47 |     100 |   95.33 | ...21-122,173-175 
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...sion-store.ts |   92.82 |     89.1 |    98.3 |   92.82 | ...1641-1647,1691 
  ...ionManager.ts |   84.52 |    83.52 |      83 |   84.52 | ...3139,3177-3178 
  ...references.ts |     100 |     90.9 |     100 |     100 | ...05,129,197,200 
  ...onSettings.ts |    92.3 |     94.4 |     100 |    92.3 | ...98-501,570-571 
  ...-converter.ts |    75.9 |    85.71 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   92.43 |    87.52 |     100 |   92.43 | ...1293-1294,1304 
  http-client.ts   |   84.61 |       80 |     100 |   84.61 | 20-21             
  i18n.ts          |   78.26 |       96 |      50 |   78.26 | 104-110,116-123   
  index.ts         |     100 |      100 |     100 |     100 |                   
  marketplace.ts   |   88.39 |    83.11 |     100 |   88.39 | ...08,494,507-508 
  ...ork-policy.ts |   89.72 |    90.16 |     100 |   89.72 | ...36,148-154,156 
  npm.ts           |   89.02 |    81.81 |     100 |   89.02 | ...86-688,695-700 
  override.ts      |   94.11 |    93.54 |     100 |   94.11 | 63-64,81-82       
  ...-converter.ts |   94.89 |    90.41 |     100 |   94.89 | ...50-151,222-224 
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   94.01 |    83.33 |     100 |   94.01 | ...38-344,365-366 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.95 |    84.21 |     100 |   88.95 | ...32-235,238-241 
  ...extraction.ts |   85.77 |       81 |   89.47 |   85.77 | ...02-205,260-261 
 ...ent-plugins-v1 |   84.94 |    79.51 |     100 |   84.94 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  manifest.ts      |   81.87 |    84.48 |     100 |   81.87 | ...55-156,161-174 
  mcp.ts           |   84.98 |    79.56 |     100 |   84.98 | ...88-389,419-420 
  paths.ts         |     100 |    94.44 |     100 |     100 | 59                
  skills.ts        |   82.31 |    63.88 |     100 |   82.31 | ...38-141,150-151 
 src/followup      |   84.72 |    81.87 |   86.84 |   84.72 |                   
  followupState.ts |   98.44 |    95.74 |     100 |   98.44 | 236-237           
  index.ts         |     100 |      100 |     100 |     100 |                   
  overlayFs.ts     |   96.29 |    88.88 |     100 |   96.29 | 78,108,122        
  speculation.ts   |   76.36 |     70.4 |   58.33 |   76.36 | ...42-743,750-751 
  ...onToolGate.ts |   97.97 |     87.5 |     100 |   97.97 | 105,110           
  ...nGenerator.ts |   86.11 |    87.17 |     100 |   86.11 | ...39-244,356-358 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   93.01 |    89.41 |   94.67 |   93.01 |                   
  ...eGoalStore.ts |   87.61 |    88.88 |   86.66 |   87.61 | ...85-188,196-204 
  ...t-verifier.ts |   96.27 |    91.17 |     100 |   96.27 | ...20,143-146,163 
  ...checkpoint.ts |   81.48 |    76.19 |     100 |   81.48 | ...02-105,115-118 
  ...ion-prompt.ts |     100 |      100 |     100 |     100 |                   
  goal-evidence.ts |   88.54 |    88.02 |   97.67 |   88.54 | ...1200,1223-1226 
  ...projection.ts |   66.66 |    72.97 |   33.33 |   66.66 | ...87,190,194-196 
  ...ersistence.ts |   87.36 |    85.96 |    87.5 |   87.36 | ...53-154,185-190 
  goal-protocol.ts |   96.87 |    95.65 |     100 |   96.87 | 215-216           
  goal-reducer.ts  |   95.27 |    92.82 |   97.29 |   95.27 | ...87,566,584-585 
  goal-runtime.ts  |   96.38 |    89.73 |   95.83 |   96.38 | ...1349-1350,1480 
  goal-tools.ts    |   98.38 |    94.17 |   95.83 |   98.38 | ...05-206,307-308 
  ...rn-context.ts |     100 |      100 |     100 |     100 |                   
  goal-verifier.ts |   92.46 |    93.02 |     100 |   92.46 | ...69-172,185-187 
  goal-wire.ts     |       0 |        0 |       0 |       0 | 1-28              
  goalHook.ts      |   96.91 |    92.42 |     100 |   96.91 | 115-120,221-222   
  goalJudge.ts     |   95.84 |    87.09 |     100 |   95.84 | ...55-356,448-449 
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/hooks         |   88.07 |    86.25 |   88.54 |   88.07 |                   
  ...okRegistry.ts |   86.48 |    77.08 |     100 |   86.48 | ...41-344,362-369 
  ...bortSignal.ts |     100 |      100 |     100 |     100 |                   
  context-usage.ts |     100 |      100 |     100 |     100 |                   
  ...terpolator.ts |   96.66 |    93.33 |     100 |   96.66 | 66-67             
  ...HookRunner.ts |   96.68 |    87.23 |     100 |   96.68 | 110-112,231-233   
  ...Aggregator.ts |   96.57 |    91.48 |     100 |   96.57 | ...20-321,402,404 
  ...entHandler.ts |   95.57 |    84.76 |   94.73 |   95.57 | ...1040-1041,1051 
  hookPlanner.ts   |   87.55 |    85.54 |   86.66 |   87.55 | ...22-226,233-244 
  hookRegistry.ts  |   92.53 |    85.43 |     100 |   92.53 | ...39,458,462,466 
  hookRunner.ts    |   62.65 |    72.34 |   66.66 |   62.65 | ...70-771,780-781 
  hookSystem.ts    |   87.64 |     98.5 |   70.83 |   87.64 | ...58-759,765-766 
  ...HookRunner.ts |   79.06 |    66.66 |      80 |   79.06 | ...33-434,452-456 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...edCallback.ts |     100 |      100 |     100 |     100 |                   
  ...HookRunner.ts |   94.19 |    84.37 |   81.81 |   94.19 | ...76-384,458-459 
  ...SkillHooks.ts |   78.75 |       75 |   66.66 |   78.75 | 62-66,137-152     
  ...oksManager.ts |   94.87 |    88.88 |     100 |   94.87 | ...84,325,327-329 
  ssrfGuard.ts     |   86.45 |    87.91 |     100 |   86.45 | ...85,289-295,301 
  stopHookCap.ts   |     100 |      100 |     100 |     100 |                   
  trustedHooks.ts  |      90 |    52.63 |     100 |      90 | ...53,66-67,97-98 
  types.ts         |   94.25 |    96.09 |   88.88 |   94.25 | ...46-547,632-636 
  urlValidator.ts  |     100 |      100 |     100 |     100 |                   
  ...it-context.ts |     100 |      100 |     100 |     100 |                   
 src/ide           |   76.98 |    85.03 |   79.03 |   76.98 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  detect-ide.ts    |     100 |      100 |     100 |     100 |                   
  ide-client.ts    |   69.16 |    84.65 |   68.29 |   69.16 | ...1068,1097-1105 
  ide-installer.ts |   89.06 |    79.31 |     100 |   89.06 | ...36,143-147,160 
  ideContext.ts    |     100 |      100 |     100 |     100 |                   
  process-utils.ts |   84.84 |    71.79 |     100 |   84.84 | ...37,151,193-194 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/lsp           |   58.96 |    70.67 |   66.49 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |    72.22 |   95.65 |   80.55 | ...02-504,508-514 
  ...ionFactory.ts |   42.81 |    73.07 |      50 |   42.81 | ...76-427,433-450 
  ...Normalizer.ts |   23.09 |    13.72 |   30.43 |   23.09 | ...04-905,909-924 
  ...verManager.ts |   75.73 |     80.1 |   79.66 |   75.73 | ...1346,1352-1382 
  ...eLspClient.ts |   32.78 |    81.81 |   21.05 |   32.78 | ...89-293,299-300 
  ...LspService.ts |      60 |    73.36 |   78.26 |      60 | ...1575,1635-1645 
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/mcp           |    82.3 |    77.81 |   78.33 |    82.3 |                   
  configHash.ts    |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...h-provider.ts |   86.95 |      100 |   33.33 |   86.95 | ...,93,97,101-102 
  ...h-provider.ts |   79.31 |    58.06 |     100 |   79.31 | ...26-933,940-942 
  ...en-storage.ts |   98.78 |    97.95 |     100 |   98.78 | 106-107           
  oauth-utils.ts   |   73.61 |    85.48 |    92.3 |   73.61 | ...46-366,392-421 
  ...n-provider.ts |   89.83 |       96 |   45.45 |   89.83 | ...43,147,151-152 
 .../token-storage |   82.12 |    88.48 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.71 |   95.23 |   87.08 | ...00-201,214-215 
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   68.14 |    82.35 |   64.28 |   68.14 | ...81-295,298-314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/memory        |   88.97 |    85.08 |   91.31 |   88.97 |                   
  ...y-document.ts |   89.52 |    84.61 |     100 |   89.52 | ...24-325,329-330 
  ...nel-memory.ts |   97.36 |    96.63 |   96.42 |   97.36 | ...91-293,367-368 
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    83.33 |     100 |     100 | 135,145           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   93.82 |    84.09 |     100 |   93.82 | 78-83,122,154-157 
  ...entPlanner.ts |   91.55 |    76.74 |     100 |   91.55 | ...05,118-121,296 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   90.16 |    78.76 |   94.44 |   90.16 | ...06,629,642-648 
  indexer.ts       |   94.14 |       84 |     100 |   94.14 | ...32-233,334,337 
  ...kill-agent.ts |   97.94 |    89.36 |     100 |   97.94 | 82-83,179-180     
  manager.ts       |   78.51 |    83.16 |   77.77 |   78.51 | ...1487,1500-1502 
  ...ent-config.ts |   86.99 |    82.69 |   86.36 |   86.99 | ...69,389,396-402 
  memoryAge.ts     |   90.47 |    84.61 |     100 |   90.47 | 50-51             
  ...yDiscovery.ts |   93.42 |    90.72 |     100 |   93.42 | ...11,370,592-595 
  paths.ts         |     100 |      100 |     100 |     100 |                   
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   97.26 |    86.79 |     100 |   97.26 | ...10-218,222,225 
  recall.ts        |   86.86 |    86.23 |   92.85 |   86.86 | ...33-538,571-582 
  refresh.ts       |   93.58 |    89.58 |     100 |   93.58 | ...75-176,183-184 
  ...ceSelector.ts |    93.2 |    85.71 |     100 |    93.2 | ...45-146,148-149 
  remember.ts      |   98.88 |    90.19 |     100 |   98.88 | 50,70             
  scan.ts          |   93.75 |       80 |     100 |   93.75 | ...08-109,154,157 
  scopes.ts        |     100 |      100 |     100 |     100 |                   
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   76.89 |    74.07 |   72.22 |   76.89 | ...47-451,454,460 
  status.ts        |   10.52 |      100 |       0 |   10.52 | 41-98             
  store.ts         |   92.92 |    81.81 |     100 |   92.92 | ...16-117,147-148 
  ...git-status.ts |     100 |    85.71 |     100 |     100 | 27                
  ...cret-guard.ts |     100 |      100 |     100 |     100 |                   
  ...emory-sync.ts |   94.24 |    82.85 |     100 |   94.24 | ...34-236,246-247 
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ontextFile.ts |   81.21 |     79.1 |   81.81 |   81.21 | ...66-280,294-299 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   92.82 |    89.39 |   91.35 |   92.82 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   97.77 |    91.83 |     100 |   97.77 | 155,161,171       
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   79.43 |    64.51 |   85.71 |   79.43 | ...,89-96,131-142 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.11 |     100 |     100 | 177,262           
  modelsConfig.ts  |   89.36 |    86.93 |   88.09 |   89.36 | ...1407,1436-1437 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/output        |     100 |      100 |     100 |     100 |                   
  ...-formatter.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/permissions   |   83.79 |    91.18 |   71.07 |   83.79 |                   
  autoMode.ts      |   97.66 |    93.13 |     100 |   97.66 | ...82-589,635,712 
  ...transcript.ts |      98 |       84 |     100 |      98 | 200-201           
  classifier.ts    |      94 |    94.54 |     100 |      94 | 158-165,389-393   
  ...erousRules.ts |     100 |    89.36 |     100 |     100 | 110,133,147,175   
  ...alTracking.ts |     100 |      100 |     100 |     100 |                   
  ...e-commands.ts |   86.77 |     73.8 |     100 |   86.77 | 131-141,210-214   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...on-manager.ts |   86.63 |    88.88 |      80 |   86.63 | ...1111,1217-1221 
  rule-parser.ts   |   94.49 |    92.77 |     100 |   94.49 | ...1447,1481-1483 
  ...-semantics.ts |   70.44 |    91.09 |   46.66 |   70.44 | ...2237,2311-2314 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...sifier-prompts |   99.04 |    95.23 |     100 |   99.04 |                   
  system-prompt.ts |   99.04 |    95.23 |     100 |   99.04 | 220               
 src/prompts       |   83.63 |      100 |    87.5 |   83.63 |                   
  mcp-prompts.ts   |   18.18 |      100 |       0 |   18.18 | 11-19             
  ...t-registry.ts |     100 |      100 |     100 |     100 |                   
 src/providers     |   83.78 |    78.34 |   81.25 |   83.78 |                   
  all-providers.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  install.ts       |   93.11 |     84.5 |     100 |   93.11 | ...56-257,330-331 
  ...der-config.ts |   75.91 |    73.48 |   78.26 |   75.91 | ...74-475,503-504 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   98.04 |    91.66 |   63.63 |   98.04 |                   
  ...oding-plan.ts |   87.34 |      100 |       0 |   87.34 | 81-83,86-88,90-93 
  ...a-standard.ts |     100 |      100 |     100 |     100 |                   
  ...token-plan.ts |     100 |      100 |     100 |     100 |                   
  ...m-provider.ts |   97.05 |    81.25 |      75 |   97.05 | 118-119           
  deepseek.ts      |     100 |      100 |     100 |     100 |                   
  grok.ts          |     100 |      100 |     100 |     100 |                   
  idealab.ts       |     100 |      100 |     100 |     100 |                   
  minimax.ts       |     100 |      100 |     100 |     100 |                   
  modelscope.ts    |     100 |      100 |     100 |     100 |                   
  moonshot.ts      |     100 |      100 |     100 |     100 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   85.36 |    78.59 |   95.94 |   85.36 |                   
  ...tGenerator.ts |    98.6 |    98.14 |     100 |    98.6 | 103-104           
  qwenOAuth2.ts    |   82.79 |    73.45 |    90.9 |   82.79 | ...1205-1221,1251 
  ...kenManager.ts |   85.36 |     76.8 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |    90.7 |     86.3 |   96.82 |    90.7 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   98.48 |    87.28 |     100 |   98.48 | 81-82,105,474-475 
  branch-points.ts |     100 |    95.23 |     100 |     100 | ...20,211,224,327 
  ...ionService.ts |   97.72 |    96.53 |     100 |   97.72 | ...1081,1224-1232 
  ...ingService.ts |   92.43 |    87.77 |   94.73 |   92.43 | ...2843,2858-2859 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |    97.2 |    94.23 |     100 |    97.2 | ...39-340,378-381 
  cronScheduler.ts |   94.17 |    90.45 |      98 |   94.17 | ...1333,1736-1737 
  cronTasksFile.ts |   95.52 |    90.99 |     100 |   95.52 | ...37,346-347,483 
  cronTasksLock.ts |   94.44 |    89.47 |     100 |   94.44 | ...02-103,132-133 
  ...eryService.ts |   96.22 |    93.54 |      90 |   96.22 | 121,155-156,161   
  ...oryService.ts |   88.17 |    79.02 |    92.3 |   88.17 | ...1303,1344-1347 
  fileReadCache.ts |    97.5 |    96.07 |     100 |    97.5 | 349-350,363-364   
  ...temService.ts |    92.8 |    84.68 |   94.11 |    92.8 | ...53,479-486,531 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |   74.75 |    70.76 |   96.07 |   74.75 | ...2296,2325-2326 
  ...on-service.ts |   86.58 |    74.39 |     100 |   86.58 | ...56-460,498-499 
  ...references.ts |   98.57 |    91.42 |     100 |   98.57 | 156-157,217-218   
  ...ionService.ts |   98.26 |    97.23 |     100 |   98.26 | ...65-866,889-890 
  ...ticsDumper.ts |   98.37 |    95.23 |     100 |   98.37 | 185-186           
  ...ureMonitor.ts |   95.82 |    90.52 |   97.05 |   95.82 | ...60,861,875-877 
  ...orRegistry.ts |   97.22 |    90.99 |     100 |   97.22 | ...55-456,609-610 
  ...ttachments.ts |   97.74 |     90.9 |     100 |   97.74 | 298-308,646       
  ...pi-history.ts |   98.94 |    89.13 |     100 |   98.94 | 43                
  ...ersistence.ts |   91.67 |    80.64 |     100 |   91.67 | ...1062-1063,1091 
  ...tory-state.ts |     100 |       95 |     100 |     100 | 31                
  ...on-service.ts |   94.61 |    92.44 |   97.22 |   94.61 | ...11-613,669-677 
  ...pr-service.ts |   96.26 |    89.13 |     100 |   96.26 | 90-93             
  ...ce-service.ts |    98.5 |    94.11 |    90.9 |    98.5 | 64-65             
  ...n-registry.ts |   98.73 |    96.29 |     100 |   98.73 | 584,638-639,692   
  ...ken-counts.ts |     100 |       96 |     100 |     100 | 58                
  ...ipt-reader.ts |    93.7 |    91.09 |    97.8 |    93.7 | ...2791-2792,2869 
  ...turn-state.ts |   94.11 |     90.9 |   91.66 |   94.11 | 108-112,129-130   
  ...est-helper.ts |       0 |        0 |       0 |       0 | 1-65              
  ...iter-lease.ts |   83.14 |    74.47 |   97.61 |   83.14 | ...2433,2445-2448 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   90.72 |    86.62 |   94.28 |   90.72 | ...3530-3531,3572 
  sessionTitle.ts  |   96.35 |    79.71 |     100 |   96.35 | ...08-311,342-343 
  ...ContextEnv.ts |     100 |    94.73 |     100 |     100 | 76,111            
  ...ionService.ts |   84.43 |    78.45 |   97.18 |   84.43 | ...2496,2502-2507 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...e-encoding.ts |   85.96 |    76.47 |     100 |   85.96 | 58-61,64-65,78-79 
  ...Estimation.ts |     100 |    94.11 |     100 |     100 | 118               
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...ite-origin.ts |     100 |    93.33 |     100 |     100 | 32                
  ...UseSummary.ts |   94.63 |    88.46 |     100 |   94.63 | ...62-164,214-215 
  ...rd-service.ts |     100 |    88.37 |     100 |     100 | ...29,145-146,241 
  ...oryService.ts |   90.77 |    84.92 |     100 |   90.77 | ...43-546,598-599 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   88.36 |     87.7 |     100 |   88.36 | ...48-449,465-466 
 ...icrocompaction |   98.91 |     95.1 |     100 |   98.91 |                   
  microcompact.ts  |   98.91 |     95.1 |     100 |   98.91 | ...60,769,778-779 
 ...s/visionBridge |    98.8 |    92.12 |     100 |    98.8 |                   
  ...capability.ts |     100 |      100 |     100 |     100 |                   
  ...part-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |   98.72 |    82.35 |     100 |   98.72 | 65,71             
  ...ge-service.ts |   98.61 |     94.7 |     100 |   98.61 | ...06,666,679-680 
 src/skills        |   89.77 |    86.05 |   94.73 |   89.77 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...activation.ts |     100 |    93.33 |     100 |     100 | 93,112            
  skill-curator.ts |   89.71 |    81.54 |     100 |   89.71 | ...01-902,904-907 
  skill-load.ts    |   94.84 |    87.69 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   86.09 |    85.64 |   86.11 |   86.09 | ...1243,1250-1254 
  skill-paths.ts   |   90.42 |     87.5 |     100 |   90.42 | ...19-120,125-126 
  symlinkScope.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |   97.91 |    98.07 |     100 |   97.91 | 277-278           
 ...ataviz/scripts |   80.06 |    95.23 |   88.23 |   80.06 |                   
  ...te_palette.js |   80.06 |    95.23 |   88.23 |   80.06 | 261-296,306-328   
 ...s/bundled/loop |   97.48 |    95.77 |     100 |   97.48 |                   
  ...omous-loop.ts |     100 |      100 |     100 |     100 |                   
  ...-task-file.ts |   94.85 |     92.4 |     100 |   94.85 | ...56,367,375-376 
  ...k-resolver.ts |     100 |      100 |     100 |     100 |                   
 src/subagents     |   88.56 |    89.42 |    98.3 |   88.56 |                   
  ...ter-schema.ts |     100 |    98.07 |     100 |     100 | 99                
  ...tin-agents.ts |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nt-manager.ts |   85.51 |    86.52 |   97.43 |   85.51 | ...1583,1660-1661 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   82.73 |     84.6 |   86.17 |   82.73 |                   
  ...ty-tracker.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...on-metrics.ts |   99.08 |    80.95 |     100 |   99.08 | 185,199           
  ...on-tracing.ts |   80.71 |    81.91 |   79.16 |   80.71 | ...92,499-501,517 
  ...attributes.ts |   96.98 |    91.37 |     100 |   96.98 | ...47-348,366-367 
  ...ag-metrics.ts |     100 |    77.77 |     100 |     100 | 21,40             
  ...t-loop-lag.ts |   96.85 |    85.71 |     100 |   96.85 | 170-173           
  ...-exporters.ts |   65.38 |    83.33 |      50 |   65.38 | ...08-109,112-113 
  ...ai-content.ts |    74.5 |    66.41 |   91.66 |    74.5 | ...1480,1493-1502 
  ...i-provider.ts |     100 |    99.02 |     100 |     100 | 106               
  ...ai-request.ts |   87.52 |    92.79 |   83.78 |   87.52 | ...55-561,564-570 
  gen-ai-usage.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-111             
  ...-processor.ts |   99.12 |    96.03 |      95 |   99.12 | 150,379-380       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   60.73 |    78.01 |   66.66 |   60.73 | ...1507,1524-1544 
  metrics.ts       |   80.37 |    82.35 |   80.95 |   80.37 | ...1150,1153-1164 
  otlp-urls.ts     |     100 |      100 |     100 |     100 |                   
  ...attributes.ts |     100 |      100 |     100 |     100 |                   
  ...ime-config.ts |       0 |        0 |       0 |       0 | 1                 
  sanitize.ts      |      80 |    83.33 |     100 |      80 | 35-36,41-42       
  ...rters-grpc.ts |     100 |      100 |     100 |     100 |                   
  ...rters-http.ts |     100 |      100 |     100 |     100 |                   
  sdk-impl.ts      |   93.95 |    86.44 |      75 |   93.95 | ...41,483-484,500 
  sdk.ts           |    82.7 |     90.9 |   66.66 |    82.7 | ...00-204,242-264 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...ion-events.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |   91.17 |    88.72 |    97.5 |   91.17 | ...1920,1949-1952 
  ...etry-utils.ts |     100 |      100 |     100 |     100 |                   
  ...l-decision.ts |     100 |      100 |     100 |     100 |                   
  trace-context.ts |     100 |      100 |     100 |     100 |                   
  ...e-id-utils.ts |     100 |      100 |     100 |     100 |                   
  tracer.ts        |   98.56 |    88.63 |     100 |   98.56 | 52,101            
  types.ts         |   83.26 |    88.66 |   86.36 |   83.26 | ...1467,1471-1478 
  uiTelemetry.ts   |   98.74 |    95.45 |   96.55 |   98.74 | ...26,475,622-623 
 ...ry/qwen-logger |   74.23 |     80.7 |      70 |   74.23 |                   
  event-types.ts   |       0 |        0 |       0 |       0 |                   
  qwen-logger.ts   |   74.23 |    80.53 |   69.49 |   74.23 | ...1122,1160-1161 
 src/test-utils    |   96.38 |    98.64 |   84.09 |   96.38 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  ...st-helpers.ts |   94.11 |       90 |     100 |   94.11 | 69-70             
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...mised-lock.ts |     100 |      100 |     100 |     100 |                   
  mock-tool.ts     |   94.85 |      100 |      80 |   94.85 | ...53,227-228,241 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |   87.43 |    85.87 |   90.02 |   87.43 |                   
  ...erQuestion.ts |   89.71 |    81.13 |    92.3 |   89.71 | ...66-367,374-375 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  ...ub-session.ts |   89.72 |    91.48 |   83.33 |   89.72 | ...06-307,318-325 
  cron-create.ts   |   90.64 |     93.1 |      75 |   90.64 | ...,73-74,223-231 
  cron-delete.ts   |   97.56 |      100 |   85.71 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.45 |   88.88 |   98.23 | 57-58             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  display-image.ts |   87.42 |    85.71 |    90.9 |   87.42 | ...29-134,194-195 
  edit.ts          |   82.76 |    86.88 |   82.35 |   82.76 | ...45-746,865-915 
  ...r-worktree.ts |   83.14 |    68.42 |   88.88 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |      85 |       84 |      90 |      85 | ...28-133,161-175 
  exit-worktree.ts |   83.29 |     83.8 |   94.73 |   83.29 | ...14-515,537-538 
  exitPlanMode.ts  |      95 |    85.29 |     100 |      95 | ...21-325,344,378 
  ...permission.ts |     100 |      100 |     100 |     100 |                   
  glob.ts          |   96.33 |     88.5 |     100 |   96.33 | ...24-225,373,376 
  grep.ts          |   90.73 |    86.71 |   86.36 |   90.73 | ...76-677,727-728 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  image-gen.ts     |   91.66 |    78.12 |   91.66 |   91.66 | ...13-214,221-222 
  list-agents.ts   |   94.11 |    83.33 |   85.71 |   94.11 | 31-32,47-48       
  loop-wakeup.ts   |   99.27 |     93.1 |     100 |   99.27 | 45                
  ls.ts            |   96.74 |    90.54 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.9 |    90.9 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   82.06 |    80.15 |   85.71 |   82.06 | ...3234,3236-3237 
  mcp-client.ts    |   86.08 |     87.5 |   93.93 |   86.08 | ...2483,2487-2490 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   79.21 |    85.71 |   81.57 |   79.21 | ...1341,1349-1350 
  ...ool-events.ts |       8 |        0 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |    97.5 |    93.93 |     100 |    97.5 | 178-179           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  ...ion-config.ts |     100 |      100 |     100 |     100 |                   
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |   97.95 |    92.37 |     100 |   97.95 | ...1161,1216-1217 
  ...sport-pool.ts |   83.98 |     80.3 |   88.46 |   83.98 | ...1409,1416-1420 
  ...ace-budget.ts |   87.27 |     82.6 |     100 |   87.27 | ...00-305,340-345 
  memory-config.ts |     100 |      100 |     100 |     100 |                   
  ...iable-tool.ts |     100 |    84.61 |     100 |     100 | 101,108           
  monitor.ts       |   91.82 |    83.09 |   88.46 |   91.82 | ...99,612,810-815 
  notebook-edit.ts |   85.71 |    77.39 |   82.35 |   85.71 | ...96-912,958-959 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   83.21 |    90.69 |     100 |   83.21 | 147-158,207-220   
  read-file.ts     |   95.49 |    88.61 |    87.5 |   95.49 | ...49,464,536-537 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  readManyFiles.ts |   95.79 |    81.35 |     100 |   95.79 | ...10,563,573-577 
  ...d-artifact.ts |   85.68 |    81.59 |   94.73 |   85.68 | ...1071,1095-1096 
  ...t-findings.ts |   99.08 |    93.75 |    92.3 |   99.08 | 217-219           
  ...t-shutdown.ts |    87.2 |    86.66 |   77.77 |    87.2 | ...,75-79,162-165 
  ripGrep.ts       |    94.6 |    87.34 |   95.45 |    94.6 | ...33-734,740-741 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |      80 |    89.74 |   66.66 |      80 | ...59-265,333-340 
  ...n-mcp-view.ts |   94.07 |    91.89 |    90.9 |   94.07 | 131-139           
  shell.ts         |   78.96 |    84.29 |      93 |   78.96 | ...5036,5111-5112 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   92.77 |    93.22 |   86.36 |   92.77 | ...80,584,630-652 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.75 |   83.33 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   80.43 |    86.95 |   85.71 |   80.43 | ...67,121,125-132 
  task-stop.ts     |   93.14 |    96.29 |    87.5 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.87 |     86.5 |   92.85 |   82.87 | ...54-564,588-599 
  team-create.ts   |   97.24 |    86.36 |   85.71 |   97.24 | 48-49,129-130     
  team-delete.ts   |   86.74 |    84.61 |   85.71 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.96 |   81.81 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   95.13 |    87.85 |   93.33 |   95.13 | ...23-527,540-545 
  ...repeat-key.ts |     100 |      100 |     100 |     100 |                   
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   80.35 |    81.52 |   85.71 |   80.35 | ...1017,1025-1026 
  ...-finalizer.ts |    98.1 |    92.36 |   93.33 |    98.1 | ...34-235,237-241 
  ...iagnostics.ts |   99.06 |    97.69 |   91.66 |   99.06 | 133-134,205       
  ...-retention.ts |     100 |    95.83 |     100 |     100 | 116               
  tool-search.ts   |   96.19 |    89.79 |   93.75 |   96.19 | ...09,259-264,426 
  tool-utils.ts    |   97.46 |    96.55 |     100 |   97.46 | 26-27             
  tools.ts         |   92.93 |    92.18 |      92 |   92.93 | ...64-565,581-587 
  truncation.ts    |   90.61 |    90.51 |     100 |   90.61 | ...53-461,498-504 
  ...reapproved.ts |   99.27 |    94.11 |     100 |   99.27 | 170               
  web-fetch.ts     |   96.05 |    90.54 |   96.77 |   96.05 | ...85-786,800-801 
  web-search.ts    |   90.58 |    83.57 |      80 |   90.58 | ...1025,1083-1086 
  write-file.ts    |   87.06 |    85.71 |   89.47 |   87.06 | ...29-832,869-904 
  zoom-image.ts    |   95.76 |    93.93 |    90.9 |   95.76 | 54-59,203-204     
 src/tools/agent   |   87.49 |    88.65 |   89.56 |   87.49 |                   
  agent.ts         |   86.18 |    87.83 |   87.36 |   86.18 | ...4383,4417-4427 
  fork-profile.ts  |   93.65 |       90 |     100 |   93.65 | ...33-134,171-174 
  fork-subagent.ts |   98.73 |       95 |     100 |   98.73 | 101-102,173       
 ...tools/artifact |   95.78 |    92.51 |   88.63 |   95.78 |                   
  artifact-tool.ts |   91.46 |    88.46 |   71.42 |   91.46 | ...13-314,322-325 
  ...-publisher.ts |     100 |    85.71 |     100 |     100 | 32                
  ...-publisher.ts |   96.74 |    97.72 |    87.5 |   96.74 | 29-30,156-157     
  html.ts          |     100 |    96.77 |     100 |     100 | 122               
  ...-publisher.ts |     100 |       80 |     100 |     100 | 30                
  oss-publisher.ts |    98.1 |    91.48 |     100 |    98.1 | 43-45             
  publisher.ts     |     100 |      100 |     100 |     100 |                   
 ...tools/workflow |   88.61 |    86.99 |   81.48 |   88.61 |                   
  workflow.ts      |   88.61 |    86.99 |   81.48 |   88.61 | ...51,796,798-799 
 src/utils         |   92.78 |    89.73 |   96.91 |   92.78 |                   
  LruCache.ts      |     100 |      100 |     100 |     100 |                   
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |      95 |    92.76 |     100 |      95 | ...49-550,657-661 
  auth-type.ts     |     100 |      100 |     100 |     100 |                   
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  ...ry-content.ts |   98.45 |    95.79 |     100 |   98.45 | 132-133,159-160   
  browser.ts       |   86.84 |    78.94 |     100 |   86.84 | 34,36-37,65-66    
  btwUtils.ts      |   13.95 |      100 |       0 |   13.95 | 17-31,34-55       
  bundlePaths.ts   |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.06 |    89.47 |     100 |   91.06 | ...46-147,154-155 
  ...n-branches.ts |   95.89 |    94.11 |      95 |   95.89 | ...99-500,512-525 
  ...tion-chain.ts |     100 |      100 |     100 |     100 |                   
  cronDisplay.ts   |     100 |    97.61 |     100 |     100 | 46                
  cronParser.ts    |   95.34 |    93.33 |     100 |   95.34 | 41-42,47-48,70-71 
  debugLogger.ts   |     100 |    97.18 |     100 |     100 | 79,86             
  ...qwen-model.ts |     100 |      100 |     100 |     100 |                   
  editHelper.ts    |   93.63 |     83.9 |     100 |   93.63 | ...27-428,462-463 
  editor.ts        |   97.65 |    95.45 |     100 |   97.65 | ...35-336,338-339 
  encoding.ts      |     100 |      100 |     100 |     100 |                   
  env.ts           |     100 |      100 |     100 |     100 |                   
  ...arResolver.ts |   94.28 |    88.88 |     100 |   94.28 | 28-29,125-126     
  errorParsing.ts  |     100 |      100 |     100 |     100 |                   
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   88.92 |    93.03 |      68 |   88.92 | ...92,394,410-411 
  fetch.ts         |   90.68 |    82.63 |     100 |   90.68 | ...72,483-484,503 
  ...ng-options.ts |     100 |      100 |     100 |     100 |                   
  file-identity.ts |     100 |      100 |     100 |     100 |                   
  fileUtils.ts     |   94.79 |    92.16 |   96.29 |   94.79 | ...2076,2084-2085 
  formatters.ts    |     100 |      100 |     100 |     100 |                   
  ...eUtilities.ts |    92.4 |    86.95 |     100 |    92.4 | ...52-158,168-169 
  ...rStructure.ts |   94.39 |    94.28 |     100 |   94.39 | ...29-132,343-348 
  getPty.ts        |   31.57 |       50 |     100 |   31.57 | 26-38             
  git-branches.ts  |    91.6 |    84.21 |    92.3 |    91.6 | ...90,405-410,570 
  ...fig-safety.ts |   97.01 |       80 |     100 |   97.01 | 53-54             
  git-ignore.ts    |     100 |      100 |     100 |     100 |                   
  gitDiff.ts       |   95.19 |    81.36 |     100 |   95.19 | ...1073,1419-1420 
  gitDirect.ts     |   98.84 |    94.28 |     100 |   98.84 | 234,318           
  ...noreParser.ts |   94.48 |    93.22 |     100 |   94.48 | ...23-124,158-159 
  gitUtils.ts      |   78.83 |    82.35 |    87.5 |   78.83 | ...22-123,164-215 
  github-prs.ts    |   95.74 |    82.27 |     100 |   95.74 | 216,314-322       
  iconvHelper.ts   |     100 |      100 |     100 |     100 |                   
  ...rePatterns.ts |     100 |      100 |     100 |     100 |                   
  image-view.ts    |   95.08 |    93.47 |     100 |   95.08 | ...62-166,234-238 
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  is-tool.ts       |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   96.15 |    93.69 |     100 |   96.15 | ...86-387,429-432 
  ...-detection.ts |     100 |      100 |     100 |     100 |                   
  ...iconv-lite.ts |     100 |      100 |     100 |     100 |                   
  ...simple-git.ts |   96.77 |    91.66 |     100 |   96.77 | 38                
  ...m-headless.ts |      96 |    88.88 |     100 |      96 | 34                
  ...-constants.ts |   94.28 |     92.3 |     100 |   94.28 | 66-67             
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...tProcessor.ts |   94.01 |    89.88 |     100 |   94.01 | ...47-353,445-446 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.21 |     100 |   98.96 | 154               
  ...kerChecker.ts |    90.9 |    91.66 |     100 |    90.9 | 73-79             
  notebook.ts      |   94.57 |    89.91 |   95.83 |   94.57 | ...21,333,385-387 
  openaiLogger.ts  |   91.66 |    89.74 |     100 |   91.66 | ...26-228,251-256 
  osc8.ts          |   54.26 |    64.86 |   83.33 |   54.26 | ...72-195,197-257 
  partUtils.ts     |     100 |    98.64 |     100 |     100 | 211               
  pathReader.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |   90.88 |     90.6 |     100 |   90.88 | ...28-629,631-633 
  pdf.ts           |   92.17 |    85.81 |     100 |   92.17 | ...64-565,606-611 
  ...s-liveness.ts |     100 |    93.47 |     100 |     100 | 62,72,108         
  projectPath.ts   |     100 |      100 |     100 |     100 |                   
  projectRoot.ts   |   71.73 |    78.57 |     100 |   71.73 | 54-66             
  ...ectSummary.ts |   89.62 |    72.41 |     100 |   89.62 | ...40-145,196-199 
  ...tIdContext.ts |     100 |      100 |     100 |     100 |                   
  proxyUtils.ts    |     100 |      100 |     100 |     100 |                   
  ...rDetection.ts |   71.15 |       86 |     100 |   71.15 | ...-90,96-101,147 
  ...noreParser.ts |   92.63 |    91.66 |     100 |   92.63 | ...77-178,197-198 
  rateLimit.ts     |   93.75 |    89.62 |     100 |   93.75 | ...13,218-219,262 
  ...text-range.ts |   96.98 |    87.36 |     100 |   96.98 | ...87-688,763-764 
  retry.ts         |   96.09 |    92.52 |     100 |   96.09 | ...72,563-564,582 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.63 |    97.08 |     100 |   97.63 | ...17,251-252,278 
  retryPolicy.ts   |   97.72 |    90.56 |     100 |   97.72 | 130-131           
  ripgrepUtils.ts  |   90.04 |    93.43 |   95.45 |   90.04 | ...55-565,598-599 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   84.87 |    86.71 |   96.29 |   84.87 | ...71,696,725-734 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |   97.77 |     91.3 |     100 |   97.77 | 172-173           
  safe-mode.ts     |     100 |      100 |     100 |     100 |                   
  safeJsonParse.ts |     100 |      100 |     100 |     100 |                   
  ...nStringify.ts |     100 |      100 |     100 |     100 |                   
  ...-child-env.ts |     100 |      100 |     100 |     100 |                   
  ...aConverter.ts |   98.22 |    98.01 |     100 |   98.22 | 100,102-103       
  ...aValidator.ts |   92.09 |    83.65 |   90.47 |   92.09 | ...60,882-883,896 
  ...r-launcher.ts |   96.35 |    93.97 |   85.71 |   96.35 | ...35-336,347-348 
  sedEditParser.ts |   91.78 |    92.18 |     100 |   91.78 | ...66-569,645-646 
  ...nIdContext.ts |     100 |       90 |     100 |     100 | 95                
  ...orageUtils.ts |   96.21 |    86.44 |     100 |   96.21 | ...70,386,466,485 
  ...-pager-env.ts |     100 |      100 |     100 |     100 |                   
  ...fety-rules.ts |     100 |     89.7 |     100 |     100 | ...01,304,309-311 
  shell-utils.ts   |   86.37 |    88.57 |     100 |   86.37 | ...2361,2368-2372 
  ...lAstParser.ts |    98.3 |    91.57 |     100 |    98.3 | ...1340-1342,1352 
  ...nlyChecker.ts |   96.33 |    96.57 |     100 |   96.33 | ...83-284,292-293 
  sideQuery.ts     |   86.82 |    86.66 |     100 |   86.82 | ...79-185,187-193 
  ...pEventSink.ts |     100 |       80 |     100 |     100 | 61                
  ...tGenerator.ts |     100 |      100 |     100 |     100 |                   
  ...ameContext.ts |     100 |      100 |     100 |     100 |                   
  symlink.ts       |   77.77 |    57.14 |     100 |   77.77 | 44,54-59          
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  terminal-env.ts  |      50 |      100 |       0 |      50 | 18-19             
  terminalSafe.ts  |     100 |      100 |     100 |     100 |                   
  ...Serializer.ts |   98.72 |       90 |     100 |   98.72 | 42-43,134,201-203 
  testUtils.ts     |   53.33 |      100 |   33.33 |   53.33 | ...53,59-64,70-72 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  textUtils.ts     |      65 |      100 |      75 |      65 | 56-75             
  thoughtUtils.ts  |     100 |    95.65 |     100 |     100 | 99                
  ...-converter.ts |   95.23 |    85.71 |     100 |   95.23 | 36-37             
  ...error-type.ts |     100 |      100 |     100 |     100 |                   
  ...name-utils.ts |     100 |      100 |     100 |     100 |                   
  ...ultCleanup.ts |   54.62 |    35.71 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   96.82 |    92.48 |     100 |   96.82 | ...37-342,344-349 
  ...pt-records.ts |   87.61 |    86.23 |     100 |   87.61 | ...80-484,514-529 
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...-directory.ts |    83.7 |    80.95 |    87.5 |    83.7 | ...37-238,252-253 
  ...ifact-path.ts |   94.11 |    92.85 |     100 |   94.11 | 32-33             
  ...aceContext.ts |   95.39 |    89.47 |     100 |   95.39 | ...16-317,321-322 
  xml.ts           |    97.8 |    87.69 |     100 |    97.8 | 98-99             
  yaml-parser.ts   |   83.87 |    77.27 |     100 |   83.87 | ...31-234,239-240 
 ...ils/filesearch |   83.94 |    80.75 |   94.78 |   83.94 |                   
  crawlCache.ts    |     100 |      100 |     100 |     100 |                   
  crawler.ts       |    82.9 |    76.81 |   95.08 |    82.9 | ...1563,1597-1598 
  fileSearch.ts    |   93.78 |    87.67 |     100 |   93.78 | ...71-272,274-275 
  fzfWorker.ts     |       0 |        0 |       0 |       0 | 1-109             
  ...rkerHandle.ts |   84.05 |    75.86 |      90 |   84.05 | ...30-334,340-341 
  ignore.ts        |     100 |    97.36 |     100 |     100 | 187               
  result-cache.ts  |     100 |    93.75 |     100 |     100 | 49                
 ...uest-tokenizer |    92.3 |      100 |   88.88 |    92.3 |                   
  ...ageFormats.ts |   81.81 |      100 |   66.66 |   81.81 | 56-61             
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
-------------------|---------|----------|---------|---------|-------------------

For detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.