Skip to content

feat(serve): observe daemon and child memory against real denominators - #8423

Merged
doudouOUC merged 11 commits into
mainfrom
agent/daemon-memory-observe
Aug 7, 2026
Merged

feat(serve): observe daemon and child memory against real denominators#8423
doudouOUC merged 11 commits into
mainfrom
agent/daemon-memory-observe

Conversation

@doudouOUC

@doudouOUC doudouOUC commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Scope note: this branch has absorbed two stacked PRs — #8462 (aggregate child RSS) and #8508 (the child-heap partition model). Three parts, described below in that order.

What this PR does

Turns the denominator #8245 landed into actual readings, and then models — without applying — the partition those readings exist to justify. Everything here is observation: no forced GC, no eviction, no session closure, no refusal, and no child spawn argument derived from any of it.

Part 1 — the daemon root's own pressure

runtime.memory.pressure reports level, ratio, source, and the six raw figures behind them.

Two denominators, worse one wins. The failure modes are independent: a container dies by RSS against its cgroup limit, while a process on a large host can exhaust V8's heap long before RSS is a meaningful fraction of the machine. Reporting only one hides whichever failure the deployment is heading for. source names which ratio produced the level.

unknown ≠ healthy. When neither denominator is measurable, source says unknown; level is normal there only because there is nothing to classify.

The denominator is availableMemoryMb, not effectiveBudgetMb. Pressure asks how close this process is to being killed, and what kills it is the cgroup limit or host memory. An operator's budget is a policy number; classifying against it would report critical for a daemon in no danger.

The flag. --memory-pressure-mode, off | observe, default observe. Both modes report every figure; only observe also raises the daemon_memory_pressure warning, so off leaves the top-level status rollup untouched. The thresholds are inherited from core's interactive-CLI MemoryPressureMonitor and are not yet calibrated for a long-running daemon — a deployment that alerts on status needs the reading without the verdict.

No enforce: nothing here remediates, and a value a caller can pass but never use is a dead switch. Severity is warning at every level including critical, because error would make rollupStatus return error for the whole daemon — too strong a claim to stake on uncalibrated thresholds.

Part 2 — aggregate child RSS

The root reading above stays normal on a daemon whose children are the ones growing. childRssCoverage has said primary_only since #8245 to admit that blind spot; #8245 shaped it as a single string literal so it could change without inventing a field.

runtime.memory.children reports summed RSS across every child with a live channel, plus:

  • sampled — how many children actually produced a reading. Load-bearing, not decoration: a sum with silent gaps reads exactly like an authoritative total. Its denominator is the sibling activeAcpChildren, so a shortfall is visible without the client knowing to look. sampled: 0 with rssBytes: 0 never means a measured zero.
  • oldestReadingAgeMs — how far apart the sum's parts were taken, since the staleness window lets them span 30 s. null when nothing was sampled and when every contributor predates the field, so it never means "fresh".

An enumeration change, not a new mechanism. Every bridge already caches a self-reported reading behind getChildResourceSnapshot; the sampler simply only ever called it on primaryEntry. The sum and the activeAcpChildren count are taken in one synchronous pass over the same array, gating on the same isChannelLive() predicate — so sampled <= activeAcpChildren holds by construction rather than by trusting the hook to self-gate. (A test caught that: with the sum trusting the hook, a stub that didn't self-gate got counted. The dormant-bridge stub is now deliberately unfaithful so the gate cannot be removed silently.)

What the figure is not. An over-count and an under-count at once, and the docs say so rather than calling it tree memory: summing per-process RSS double-counts shared pages, while each child reports only its own process, so its MCP descendants and every channel worker are missing. Reading RSS from the OS by PID would fix both plus hung children — the daemon does know the PIDs, spawnChannel.ts uses child.pid for the stderr prefix — but /proc is Linux-only and this ships on macOS and Windows (both in the CI matrix). Recorded as the natural upgrade, not attempted.

Not folded into pressure.ratio: imprecise in both directions, components sampled at different instants, and Part 1's thresholds are mid-calibration — changing that numerator would destroy the data being gathered.

The metrics ring's childRssBytes keeps its published singular meaning (the primary child's RSS) and is unchanged.

Part 3 — modeling a per-child heap partition

getAcpMemoryArgs() computes one ceiling from host memory and hands the same value to every child. The daemon runs one child per workspace and registers up to 25, so a 32 GB host authorises 25 × 16 GB = 400 GB of child heap — the defect #8182 records. This part does not close it. It publishes the partition that would close it, so the partition can be judged before anything depends on it.

--child-heap-mode is off | observe, default observe. Status gains limits.memory.childHeap: maxConcurrentChildren, perChildCeilingMb, and refusals (spawns that would have exceeded the modeled limit). Nothing is applied — no child is sized from the budget, no spawn is refused, and limits.memory.enforced stays the required literal false.

A fixed partition, not a per-spawn share. Sizing each child by the count live at its spawn bounds the child count but not the memory: V8 cannot lower a running child's ceiling, so grants accumulate as P + P/2 + P/3 + … = P × H(n). That was the first attempt, and review took it apart with numbers — 9557 MB authorised against a 3687 MB pool at seven children on 8 GB; 61355 MB against 15360 MB on 32 GB. The model is now one constant ceiling for every child, with admission capped so maxConcurrentChildren × perChildCeilingMb ≤ childPoolMb holds by construction, with no ledger and no dependence on arrival order.

Why there is no enforce. Its inclusion rested on "observe first, then enforce", and that path does not exist yet. While observing, children run on the host-derived ceiling — 16384 MB on a 32 GB host — so a workload needing 2 GB of old space is healthy with refusals: 0 and OOMs the instant a 614 MB partition is applied. The counter measures admission pressure, not ceiling adequacy. Deciding when enforcing is safe needs peak old-space per child (not rss, which is what the child self-reports today, and not heapUsed, which includes the new generation), measured inside the child so GC-time peaks are not missed. That is its own measurement chain, and the enforcing mode ships with it. Shipping a switch with no safe way to decide when to turn it on is worse than shipping no switch.

Consequently the machinery that existed only to apply the partition was removed rather than shipped unreachable: getAcpMemoryArgs(explicitMb?), ChildHeapPoolExhaustedError and both transport mappings, and the widening of limits.memory.enforced to boolean.

The zero-pool defect, also from review and caused by a clamp forcing at least one admissible child: a 512 MB host — where the root reserve consumes the entire 256 MB budget, leaving a pool of 0 — modeled a ceiling of 0, and --max-old-space-size=0 is V8's default heap, roughly 4 GB, not a zero ceiling. A pool that cannot cover one child at the 512 MB floor now reports maxConcurrentChildren: 0 and perChildCeilingMb: null. The test that had enshrined the old behaviour ("always admits at least one child, however small the pool") is inverted.

The sub-floor ceiling defect, from @wenshao's review of this revision. perChildCeilingMb was min(floor(pool / max), legacyChildCeilingMb); the first term clears MIN_CHILD_HEAP_MB by construction but the second is floor(available / 2) and does not, so the Math.min could publish a ceiling below the minChildHeapMb in the same snapshot — avail=768 with --memory-budget-mb 1024 modeled one child at 384 MB. Unreachable from a derived budget, because the pool hits 0 first; reachable through the explicit flag, which docs/users/qwen-serve.md recommends on exactly these hosts. The model is now refused rather than shrunk under the floor, with maxConcurrentChildren zeroed in lockstep. The existing matrix resolved derived budgets only, which is why the mutation sweep came back clean, so it gains a budgetMb axis plus the inclusive boundary (1024/1024 → one child at 512) that stops unconditional nulling from passing instead.

What an operator gets. perChildCeilingMb and maxConcurrentChildren are published so the partition can be judged against a known workload — the substitute for a counter that cannot judge it. An 8 GB host models 7 children at 526 MB; a 32 GB host models 25 at 614 MB, both pinned in tests since they are the numbers one plans against.

Docs

Six files. childRssCoverage had eight references, four carrying claims that go false once it stops reading primary_only — including two shipped type comments (the daemon's and the SDK's) that cited it as the evidence pressure is root-only. All four now state pressure's scope on its own terms and point at children.rssBytes. Also a new observability triage recipe, and the design doc's Part 2 corrected to describe what shipped.

Why it's needed

The daemon samples its own RSS and heap every 5 s but has nothing to divide them by, so no field in /daemon/status says whether a number is fine or nearly fatal. And the memory that actually matters isn't in the root process at all — per-session RSS lives in the qwen --acp children, which the daemon only ever measured for the primary workspace.

#8245 landed the denominator (limits.memory). This turns it into readings, for both.

Part 2 of the design in docs/design/2026-07-31-daemon-capacity-model-and-memory-bounds.md.

Reviewer Test Plan

How to verify

1. The root pressure reading, and that source names the denominator that won.

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.runtime.memory.pressure'

level is normal / soft / hard / critical; ratio is the worse of rssRatio and heapRatio; source says which produced it. Check source before believing level: unknown means neither denominator was measurable, so normal there is the absence of a reading, not evidence of health.

2. off reports everything and only drops the verdict. Boot once per mode and diff:

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.runtime.memory.pressure, [.issues[]?|select(.code=="daemon_memory_pressure")]'

Under --memory-pressure-mode off the pressure block must be fully populated and identical in shape, while the daemon_memory_pressure issue must be absent and the top-level status rollup unchanged. A mode that suppressed the figures too would defeat the purpose of the flag.

3. Aggregate child RSS, and the coverage flip.

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.runtime.memory | {children, activeAcpChildren, childRssCoverage}'

childRssCoverage must now read active_children (it was primary_only). The invariant that makes children.rssBytes honest:

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.runtime.memory | .children.sampled <= .activeAcpChildren'
# true

Expect sampled: 0 on an idle daemon — child-RSS sampling only runs while something is watching. To see a non-zero sum, open a workspace and hold an SSE or WS stream against it, then re-read. This is also why the e2e cannot assert a non-zero sum (see below); it is the single thing most likely to look like a bug to a reviewer who skips it.

4. sampled: 0 is never a measured zero. With two workspaces open but only one streaming, children.rssBytes must be accompanied by sampled: 1 against activeAcpChildren: 2 — the shortfall visible without knowing to look for it.

5. The modeled partition fits inside the pool it partitions.

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.limits.memory.childHeap'

On an 8 GB host expect {"mode":"observe","maxConcurrentChildren":7,"perChildCeilingMb":526,"refusals":0}; on 32 GB, 25 at 614. The invariant that makes it an aggregate bound rather than a per-spawn share holds on any host:

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.limits.memory | (.childHeap.maxConcurrentChildren * .childHeap.perChildCeilingMb) <= .modeled.childPoolMb'
# true

6. Nothing is applied — the check that matters for Part 3. Open a session so a qwen --acp child spawns, then read its argv under each mode:

ps -o args= -p "$(pgrep -f 'qwen --acp' | head -1)" | tr ' ' '\n' | grep max-old-space-size

The value must be the host-derived ceiling and byte-identical under --child-heap-mode observe and --child-heap-mode off. In particular it must not equal perChildCeilingMb. Confirm alongside it that limits.memory.enforced is still the literal false, and that the enforcing mode is genuinely absent rather than merely defaulted off:

qwen serve --child-heap-mode enforce
# error: Invalid values: Argument: child-heap-mode, Given: "enforce", Choices: "off", "observe"

7. Unit suites.

npx vitest run packages/cli/src/serve/daemon-memory-pressure.test.ts
npx vitest run packages/cli/src/serve/daemon-status.test.ts
npx vitest run packages/acp-bridge/src/child-heap-policy.test.ts

Evidence (Before & After)

N/A — no user-visible or TUI change. The observable difference is the /daemon/status payload:

  "runtime": { "memory": {
-     "childRssCoverage": "primary_only",
+     "childRssCoverage": "active_children",
+     "pressure": { "level": "normal", "ratio": 0.31, "source": "rss", ... },
+     "children": { "rssBytes": 402653184, "sampled": 2, "oldestReadingAgeMs": 4120 },
      "activeAcpChildren": 2
  } },
  "limits": { "memory": {
      "enforced": false,
+     "childHeap": { "mode": "observe", "maxConcurrentChildren": 7, "perChildCeilingMb": 526, "refusals": 0 },
      "modeled": { "childPoolMb": 3687, ... }
  } }

Child argv is unchanged in both directions — that is the point of Part 3, and step 6 above is how a reviewer confirms it.

Tested on

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

Locally on Linux:

  • 176 unit + 6 end-to-end + 2 bridge tests; prettier, eslint, and tsc on all three of cli, sdk-typescript, and acp-bridge.
  • Mutation-verified, not just green: dropping the MB→bytes conversion fails 2 tests; Math.maxMath.min on the pressure ratio fails 7; deleting the flag from the handler fails 2; sampled := activeAcpChildren fails 2; dropping the liveness gate fails 1; widening the staleness cliff fails the bridge test.
  • The mode gate is tested with a 1 MiB denominator, because at a realistic one the level is normal and the assertion would hold with the gate deleted.

For Part 3: 49 acp-bridge tests across child-heap-policy, process-registry, and spawnChannel, plus 218 in run-qwen-serve. The invariant maxConcurrentChildren × perChildCeilingMb ≤ childPoolMb is asserted across 2/8/32/256 GB hosts; observe is asserted to leave spawn argv byte-identical to a factory with no policy; enforce is asserted to be rejected by both yargs and the fast path. Two review findings against the merged revision are fixed here and both are mutation-verified: the status assertion in run-qwen-serve.test.ts was failing on head against the four-field wire shape, and decide() ran outside the try that cancels the spawn reservation, so a throwing caller-supplied policy leaked the token — reverting that move reproduces expected 1 to be +0.

macOS and Windows are left to CI. The one platform-sensitive decision is deliberate: RSS is read from each child's self-report rather than /proc, precisely because /proc is Linux-only.

Environment (optional)

npm run dev daemon on Linux; unit and e2e tests for the assertions above.

Risk & Scope

  • Main risk or tradeoff: the thresholds are inherited from the interactive CLI and are not calibrated for a long-running daemon, so level may be wrong in both directions on real deployments — which is exactly why every level maps to warning severity and never error, and why off keeps the figures while dropping the verdict. The second tradeoff is children.rssBytes being an over-count and an under-count at once (shared pages double-counted; MCP descendants and channel workers missing); it is documented as such rather than presented as tree memory, and deliberately kept out of pressure.ratio. The third belongs to Part 3: publishing a partition invites a reading it cannot support — an operator seeing refusals: 0 may conclude enforcing is safe. It is not, for the reason given under Part 3, and that caveat is now stated in the flag help, all three operator docs, and the protocol doc. The tradeoff accepted there is shipping a model that constrains nothing: bug(serve): daemon authorises each ACP child 50% of host memory, never divided by child count #8182's 400 GB overcommit stays open until the enforcing mode lands with its measurement.
  • Not validated / out of scope: observation only throughout — no forced GC, no eviction, no session closure, no refusal, and no spawn argument derived from any of it. Channel workers still report no RSS at all: they have no reporting path to reuse, which is its own change. Applying the child-heap partition is out of scope, as is the peak old-space measurement the enforcing mode depends on. Two known coverage gaps are stated rather than hidden — the sampler's fan-out has no automated coverage (no test drives that timer today, and the pre-existing primary-only refresh was equally uncovered), and the e2e cannot assert a non-zero child sum because it opens no SSE/WS stream so the sampler's watch gate never fires; it asserts the invariants that hold regardless and says why, rather than leaving a vacuous check. off publishes no partition: both maxConcurrentChildren and perChildCeilingMb are null there, distinct from the 0 that means a pool too small to host one child.
  • Breaking changes / migration notes: none — additive. pressure and children are both optional in the SDK mirror because daemons that shipped runtime.memory before them send the block without those fields; childRssCoverage is a union there for the same reason, while the daemon's own type stays a single literal. No capability tag: daemon_status is already baseline, and the two-edit gating contract covers only CONDITIONAL_SERVE_FEATURES. Clients that pinned childRssCoverage === 'primary_only' will see 'active_children' — the field was shaped as a single literal in feat(serve): resolve and report the daemon memory budget #8245 for exactly this transition. From Part 3, limits.memory.childHeap is likewise additive and limits.memory.enforced remains the required literal false: an earlier revision widened it to boolean, and that widening was reverted, so no client contract changes. --child-heap-mode is a new flag defaulting to observe; enforce is not an accepted value.

Linked Issues

Refs #8051 (Parts 1–2) and #8182 (Part 3). Supersedes #8462 and #8508, both merged into this branch.

中文说明

范围说明: 本分支已吸收两个叠加的 PR——#8462(子进程 RSS 汇总)与 #8508(子进程堆分区模型)。共三部分,按下述顺序说明。

这个 PR 做了什么

#8245 落地的分母变成真正的读数,并对这些读数所要支撑的那份分区做出建模——但不应用它。这里的一切都是观测:不强制 GC、不驱逐、不关闭 session、不拒绝请求,也没有任何子进程派生参数由此推导。

Part 1 —— daemon 根进程自身的压力

runtime.memory.pressure 报告 levelratiosource,以及支撑它们的六个原始数值。

两个分母,取更差者。 两种失效模式相互独立:容器是因 RSS 触及 cgroup 限制而死,而大内存主机上的进程可能在 RSS 还只占机器一小部分时就耗尽 V8 堆。只报其一,就会掩盖该部署正在走向的那种失效。source 指明是哪个比值产生了 level

unknown ≠ 健康。 当两个分母都无法测量时,sourceunknown;此时 level 之所以是 normal,仅仅因为没有可分类的东西。

分母是 availableMemoryMb,不是 effectiveBudgetMb 压力问的是「这个进程离被杀有多近」,而杀死它的是 cgroup 限制或主机内存。运维的预算是一个策略数字;用它来分类会让毫无危险的 daemon 报出 critical

这个 flag。 --memory-pressure-mode,取值 off | observe,默认 observe。两种模式都报告全部数值;只有 observe 会额外抛出 daemon_memory_pressure 警告,因此 off 不会触动顶层 status 汇总。阈值继承自 core 面向交互式 CLI 的 MemoryPressureMonitor,尚未针对长运行 daemon 校准——依据 status 告警的部署需要读数,但不需要那个判定。

没有 enforce:这里不做任何补救,而一个调用方能传却永远用不上的值就是死开关。所有级别(包括 critical)的 severity 都是 warning,因为 error 会让 rollupStatus 把整个 daemon 判为 error——用未校准的阈值下这么重的结论太过了。

Part 2 —— 子进程 RSS 汇总

当增长发生在子进程时,上面的根进程读数会一直保持 normal。自 #8245childRssCoverage 就一直是 primary_only,用以承认这个盲区;#8245 把它设计成单个字符串字面量,正是为了将来可以改变而无需新增字段。

runtime.memory.children 报告所有持有活跃 channel 的子进程的 RSS 汇总,另加:

  • sampled —— 实际产出了读数的子进程数。这是承重字段而非装饰:一个带有静默缺口的求和,读起来和权威总数一模一样。它的分母是同级的 activeAcpChildren,因此缺口无需客户端刻意查找即可见。sampled: 0rssBytes: 0 绝不表示测得为零。
  • oldestReadingAgeMs —— 求和的各部分采样时刻相隔多远,因为陈旧窗口允许它们跨越 30 秒。当没有任何采样时为 null,当所有贡献者都早于该字段存在时也为 null,所以它绝不意味着「新鲜」。

这是枚举方式的改变,不是新机制。 每个 bridge 早就在 getChildResourceSnapshot 背后缓存了自上报读数;采样器只是从来只对 primaryEntry 调用它。求和与 activeAcpChildren 计数在同一次对同一数组的同步遍历中取得,并依据同一个 isChannelLive() 谓词做门控——因此 sampled <= activeAcpChildren 是按构造成立的,而不是靠信任 hook 自行门控。(一个测试抓到了这点:当求和信任 hook 时,一个不自行门控的 stub 被计入了。现在那个休眠 bridge 的 stub 被刻意做得不忠实,好让这道门控无法被静默移除。)

这个数字不是什么。 它同时既高估又低估,文档如实说明而非把它称作进程树内存:按进程求和 RSS 会重复计算共享页,而每个子进程只报告自己这一个进程,所以它的 MCP 子孙进程和所有 channel worker 都缺失。按 PID 从操作系统读 RSS 能同时修正这两点外加卡死的子进程——daemon 确实知道这些 PID,spawnChannel.ts 就用 child.pid 做 stderr 前缀——但 /proc 仅限 Linux,而本项目发布到 macOS 与 Windows(两者都在 CI 矩阵中)。这被记录为顺理成章的后续升级,本次未做。

未并入 pressure.ratio:它在两个方向上都不精确,各分量采样时刻不同,且 Part 1 的阈值正在校准中——改动那个分子会毁掉正在收集的数据。

metrics ring 的 childRssBytes 保持其已发布的单数含义(主 workspace 子进程的 RSS),未作改动。

Part 3 —— 对子进程堆分区建模

getAcpMemoryArgs() 从主机内存算出一个上限,然后把同一个值发给每个子进程。daemon 每个 workspace 跑一个子进程,最多注册 25 个,于是一台 32 GB 主机授权了 25 × 16 GB = 400 GB 的子进程堆——这就是 #8182 记录的缺陷。本部分并未关闭它,而是把能关闭它的那份分区发布出来,好让这份分区在被依赖之前先接受检验。

--child-heap-mode 取值 off | observe,默认 observe。状态新增 limits.memory.childHeapmaxConcurrentChildrenperChildCeilingMb,以及 refusals(本会超出建模上限的派生次数)。不应用任何东西——没有子进程按预算调整大小,没有派生被拒绝,limits.memory.enforced 保持必需的字面量 false

固定分区,而非按派生分摊。各自派生时刻的存活数给每个子进程定额,约束的是子进程数量而非内存:V8 无法下调运行中子进程的上限,于是授权累加为 P + P/2 + P/3 + … = P × H(n)。第一版就是这么做的,评审用数字把它拆掉了——8 GB 主机上七个子进程时,对 3687 MB 的池授权了 9557 MB;32 GB 上是 61355 MB 对 15360 MB。模型现在是给每个子进程一个恒定上限,并对准入设上限,使 maxConcurrentChildren × perChildCeilingMb ≤ childPoolMb 按构造成立,无需账本,也不依赖到达顺序。

为什么没有 enforce 它的存在依赖「先观察、再强制」,而这条路径尚不存在。观察期间子进程跑在主机推导的上限上——32 GB 主机是 16384 MB——所以一个需要 2 GB 老生代的负载在 refusals: 0 下完全健康,而一旦应用 614 MB 的分区就会立刻 OOM。这个计数衡量的是准入压力,不是上限是否够用。要判断何时强制是安全的,需要每个子进程的峰值老生代(不是子进程今天自上报的 rss,也不是包含新生代的 heapUsed),且必须在子进程内部测量,否则会漏掉 GC 时刻的峰值。那本身是一条独立的测量链,强制模式将与它一同发布。发布一个无法安全判断何时开启的开关,比不发布这个开关更糟。

因此那些只为应用分区而存在的机制被移除,而非以不可达的形式发布:getAcpMemoryArgs(explicitMb?)ChildHeapPoolExhaustedError 及其两处传输层映射,以及把 limits.memory.enforced 放宽为 boolean 的那次改动。

零池缺陷,同样来自评审,由「强制至少允许一个子进程」的钳位导致:512 MB 主机——root 预留吃掉了全部 256 MB 预算,池为 0——会建模出上限 0,而 --max-old-space-size=0 表示 V8 的默认堆,约 4 GB,并非零上限。现在,连一个 512 MB 下限子进程都容纳不下的池会报告 maxConcurrentChildren: 0perChildCeilingMb: null。那个把旧行为固化成预期的测试("always admits at least one child, however small the pool")已被反转。

低于下限的上限缺陷,来自 @wenshao 对本版本的评审。perChildCeilingMb 原为 min(floor(pool / max), legacyChildCeilingMb);前一项按构造不低于 MIN_CHILD_HEAP_MB,后一项是 floor(available / 2) 则不然,于是 Math.min 可能发布出一个低于同一快照中 minChildHeapMb 的上限——avail=768 配合 --memory-budget-mb 1024 会建模出一个 384 MB 的子进程。从推导预算无法到达,因为池会先归零;经由显式标志可以到达,而 docs/users/qwen-serve.md 恰恰建议在这类主机上使用该标志。现在的做法是拒绝建模,而不是把上限压到下限之下,并同步把 maxConcurrentChildren 归零。原有的参数矩阵只解析推导预算,这正是变异扫描当初一片绿的原因,因此它新增了 budgetMb 这一维,外加闭区间边界(1024/1024 → 一个子进程 512 MB),以阻止「无条件置 null」蒙混过关。

运维实际得到什么。 发布 perChildCeilingMbmaxConcurrentChildren,是为了让这份分区能对照已知负载来判断——这是那个无法做判断的计数的替代品。8 GB 主机建模为 7 个子进程、每个 526 MB;32 GB 建模为 25 个、每个 614 MB,两者都在测试中固定,因为这正是做容量规划时依据的数字。

文档

六个文件。childRssCoverage八处引用,其中四处所载的说法在它不再是 primary_only 之后就会变假——包括两处已发布的类型注释(daemon 的与 SDK 的),它们把该字段引作压力读数仅覆盖根进程的证据。这四处现在都以压力自身的口径陈述其覆盖范围,并指向 children.rssBytes。另有一份新的可观测性排查配方,以及设计文档 Part 2 的修订,使其描述实际发布的内容。

为什么需要它

daemon 每 5 秒采样自身的 RSS 与堆,却没有任何东西可以做除数,因此 /daemon/status 里没有任何字段能说明某个数字是正常还是濒临致命。而且真正要紧的内存根本不在根进程里——每个 session 的 RSS 位于 qwen --acp 子进程中,而 daemon 从来只测量主 workspace 那一个。

#8245 落地了分母(limits.memory)。本 PR 把它变成读数,两边都覆盖。

设计文档 docs/design/2026-07-31-daemon-capacity-model-and-memory-bounds.md 的 Part 2。

评审者测试计划

如何验证

1. 根进程压力读数,以及 source 指明胜出的分母。

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.runtime.memory.pressure'

levelnormal / soft / hard / criticalratiorssRatioheapRatio 中较差者;source 说明是哪个产生的。相信 level 之前先看 sourceunknown 表示两个分母都无法测量,此时的 normal 是读数的缺失,而非健康的证据。

2. off 报告全部数值,只去掉判定。 分别以两种模式启动并对比:

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.runtime.memory.pressure, [.issues[]?|select(.code=="daemon_memory_pressure")]'

--memory-pressure-mode off 下,pressure 块必须完整填充且结构一致,而 daemon_memory_pressure 这条 issue 必须缺席,顶层 status 汇总不变。一个连数值也一并压制的模式会让这个 flag 失去意义。

3. 子进程 RSS 汇总,以及覆盖范围的翻转。

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.runtime.memory | {children, activeAcpChildren, childRssCoverage}'

childRssCoverage 现在必须是 active_children(原为 primary_only)。让 children.rssBytes 诚实的那个不变量:

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.runtime.memory | .children.sampled <= .activeAcpChildren'
# true

空闲 daemon 上预期 sampled: 0 —— 子进程 RSS 采样只在有观察者时才运行。要看到非零的汇总,需要打开一个 workspace 并保持一条 SSE 或 WS 流,然后重新读取。这也是 e2e 无法断言非零汇总的原因(见下);这一点最容易被跳过它的评审者误认为是 bug。

4. sampled: 0 绝非测得为零。 在打开两个 workspace 但只有一个在流式传输的情况下,children.rssBytes 必须伴随 sampled: 1activeAcpChildren: 2——缺口无需刻意寻找即可见。

5. 建模分区落在它所划分的池内。

curl -s 'http://127.0.0.1:4170/daemon/status' | jq '.limits.memory.childHeap'

8 GB 主机上预期 {"mode":"observe","maxConcurrentChildren":7,"perChildCeilingMb":526,"refusals":0};32 GB 上是 25614。使其成为总量约束(而非按派生分摊)的那个不变量在任何主机上都成立:

curl -s 'http://127.0.0.1:4170/daemon/status' | \
  jq '.limits.memory | (.childHeap.maxConcurrentChildren * .childHeap.perChildCeilingMb) <= .modeled.childPoolMb'
# true

6. 什么都没被应用——这是 Part 3 最关键的检查。 打开一个 session 让 qwen --acp 子进程派生出来,然后在两种模式下分别读它的 argv:

ps -o args= -p "$(pgrep -f 'qwen --acp' | head -1)" | tr ' ' '\n' | grep max-old-space-size

该值必须是主机推导的上限,并且在 --child-heap-mode observe--child-heap-mode off逐字节一致,尤其不能等于 perChildCeilingMb。同时确认 limits.memory.enforced 仍是字面量 false,以及强制模式是真的不存在而非只是默认关闭:

qwen serve --child-heap-mode enforce
# error: Invalid values: Argument: child-heap-mode, Given: "enforce", Choices: "off", "observe"

7. 单元测试套件。

npx vitest run packages/cli/src/serve/daemon-memory-pressure.test.ts
npx vitest run packages/cli/src/serve/daemon-status.test.ts
npx vitest run packages/acp-bridge/src/child-heap-policy.test.ts

证据(前后对比)

N/A —— 无用户可见或 TUI 变更。可观察的差异是 /daemon/status 的载荷:

  "runtime": { "memory": {
-     "childRssCoverage": "primary_only",
+     "childRssCoverage": "active_children",
+     "pressure": { "level": "normal", "ratio": 0.31, "source": "rss", ... },
+     "children": { "rssBytes": 402653184, "sampled": 2, "oldestReadingAgeMs": 4120 },
      "activeAcpChildren": 2
  } },
  "limits": { "memory": {
      "enforced": false,
+     "childHeap": { "mode": "observe", "maxConcurrentChildren": 7, "perChildCeilingMb": 526, "refusals": 0 },
      "modeled": { "childPoolMb": 3687, ... }
  } }

子进程 argv 在两个方向上都未改变——这正是 Part 3 的重点,上面第 6 步就是评审者确认它的方式。

测试平台

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

Linux 本地:

  • 176 个单元测试 + 6 个端到端 + 2 个 bridge 测试;prettier、eslint,以及对 clisdk-typescriptacp-bridge 三者的 tsc
  • 经变异验证,不只是「跑绿」:去掉 MB→字节 换算会挂 2 个测试;把压力比值的 Math.max 改成 Math.min 会挂 7 个;从 handler 里删掉该 flag 会挂 2 个;令 sampled := activeAcpChildren 会挂 2 个;去掉存活门控会挂 1 个;放宽陈旧窗口会挂掉那个 bridge 测试。
  • 模式门控用 1 MiB 的分母来测,因为在现实分母下 level 恒为 normal,即便删掉门控该断言也照样成立。

Part 3 部分:child-heap-policyprocess-registryspawnChannel 三个套件共 49 个 acp-bridge 测试,外加 run-qwen-serve 的 218 个。不变量 maxConcurrentChildren × perChildCeilingMb ≤ childPoolMb 在 2/8/32/256 GB 主机规格上均有断言;observe 被断言使派生 argv 与「无策略工厂」逐字节一致;enforce 被断言在 yargs 与 fast path 两处均被拒绝。针对已合并版本的两条评审意见在此修复,且均经变异验证:run-qwen-serve.test.ts 中的状态断言在 head 上对四字段的 wire 形状是失败的;以及 decide() 运行在取消派生预留的 try 之外,导致调用方提供的策略一旦抛出就会泄漏预留令牌——把该改动回退即可复现 expected 1 to be +0

macOS 与 Windows 交由 CI。唯一与平台相关的决策是刻意为之:RSS 取自各子进程的自上报而非 /proc,正是因为 /proc 仅限 Linux。

环境(可选)

Linux 上的 npm run dev daemon;上述断言涉及单元测试与 e2e。

风险与范围

  • 主要风险或权衡: 阈值继承自交互式 CLI,未针对长运行 daemon 校准,因此在真实部署中 level 可能在两个方向上都不准——这恰恰是所有级别都映射为 warning 而绝不用 error 的原因,也是 off 保留数值却去掉判定的原因。第二个权衡是 children.rssBytes 同时既高估又低估(共享页被重复计算;MCP 子孙进程与 channel worker 缺失);文档如实说明而非将其呈现为进程树内存,并刻意不并入 pressure.ratio。第三个属于 Part 3:发布一份分区会招致它支撑不了的解读——运维看到 refusals: 0 可能会认为强制是安全的。并非如此,原因见 Part 3,该警告现已写入 flag 帮助、三份运维文档和协议文档。那里接受的权衡是:发布一个不约束任何东西的模型——bug(serve): daemon authorises each ACP child 50% of host memory, never divided by child count #8182 的 400 GB 超额授权在强制模式连同其测量手段落地之前仍然敞开。
  • 未验证 / 超出范围: 全程仅观测——不强制 GC、不驱逐、不关闭 session、不拒绝请求,也没有任何派生参数由此推导。channel worker 仍完全不上报 RSS:它们没有可复用的上报路径,那是独立的一项改动。应用子进程堆分区超出范围,强制模式所依赖的峰值老生代测量同样超出范围。两处已知覆盖缺口如实陈述而非隐藏——采样器的 fan-out 没有自动化覆盖(今天没有测试驱动那个定时器,而原有的 primary-only 刷新同样没有覆盖),以及 e2e 无法断言非零的子进程汇总,因为它不开启 SSE/WS 流,采样器的观察门控永不触发;它断言那些无论如何都成立的不变量并说明原因,而不是留下一个空洞的检查。off 不发布任何分区:maxConcurrentChildrenperChildCeilingMb 在该模式下均为 null,与「池小到容不下一个子进程」所对应的 0 相区分。
  • 破坏性变更 / 迁移说明: 无——纯新增。pressurechildren 在 SDK 镜像中均为可选,因为在它们之前就发布了 runtime.memory 的 daemon 会发送不含这些字段的块;childRssCoverage 出于同样原因在那里是联合类型,而 daemon 自身的类型保持单一字面量。无 capability 标记:daemon_status 已是基线,且两处编辑的门控契约只覆盖 CONDITIONAL_SERVE_FEATURES。曾把 childRssCoverage === 'primary_only' 写死的客户端将看到 'active_children'——feat(serve): resolve and report the daemon memory budget #8245 把该字段设计成单一字面量,正是为了这次过渡。来自 Part 3 的 limits.memory.childHeap 同样是新增字段,limits.memory.enforced 仍为必需的字面量 false:早前版本曾将其放宽为 boolean,该放宽已回退,因此客户端契约没有变化。--child-heap-mode 是默认 observe 的新 flag;enforce 不是可接受的取值。

关联 Issue

Refs #8051(Part 1–2)与 #8182(Part 3)。取代 #8462#8508,两者均已合入本分支。

🤖 Generated with Claude Code

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR — the description itself is unusually thorough, but it doesn't follow the PR template, so the gate stops here before any code review.

The body uses freeform sections (## Why, ## What, ## The flag, ## Scope, ## Compatibility, ## Verification) and is missing every required heading from pull_request_template.md:

  • ## What this PR does
  • ## Why it's needed
  • ## Reviewer Test Plan — including ### How to verify, ### Evidence (Before & After), and the ### Tested on OS matrix
  • ## Risk & Scope
  • ## Linked Issues
  • The Chinese translation inside the template's <details> block

Part 1 of this series (#8245) followed the template to the letter, so this reads as an oversight rather than missing information — nearly all of the content is already written and just needs to be reshaped into the template's sections. Two concrete gaps the reshaping should close: the "Tested on" table (the Verification section doesn't say which OS the 174 unit + 6 e2e tests ran on), and explicit Linked Issues entries for #8051 / #8245, which are currently referenced only in prose.

Once the body follows the template, push an update (or re-run with @qwen-code /triage) and it will go through the gate again.

@doudouOUC

中文说明

感谢贡献——PR 描述本身写得相当详实,但没有遵循 PR 模板,因此门禁在代码审查之前就停在这里。

正文使用了自由格式的小节(## Why## What## The flag## Scope## Compatibility## Verification),缺少 pull_request_template.md 要求的全部标题:

  • ## What this PR does
  • ## Why it's needed
  • ## Reviewer Test Plan —— 包括 ### How to verify### Evidence (Before & After)### Tested on 操作系统矩阵
  • ## Risk & Scope
  • ## Linked Issues
  • 模板 <details> 块中的中文翻译

本系列的第 1 部分(#8245)是严格按模板写的,所以这更像是疏忽而不是信息缺失——绝大部分内容已经写好了,只需要重新组织成模板的结构。重整时应补上两个具体缺口:"Tested on" 表格(Verification 一节没有说明 174 个单测 + 6 个端到端测试是在哪些操作系统上跑的),以及把 #8051 / #8245 写进显式的 Linked Issues(目前只在正文里提到)。

正文符合模板后,推送更新(或用 @qwen-code /triage 重跑)即可重新过门禁。

Qwen Code · qwen3.8-max-preview

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head a909ff5, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 4 scenario(s).

Qwen Code · serve A/B

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Code Coverage Summary

Package Lines Statements Functions Branches
CLI 83.34% 83.34% 89.4% 82.45%
Core 87.67% 87.67% 89.27% 86.24%
CLI Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   83.34 |    82.45 |    89.4 |   83.34 |                   
 src               |   84.77 |    80.78 |   88.39 |   84.77 |                   
  cli.ts           |   95.68 |    84.11 |     100 |   95.68 | ...60-561,565-566 
  gemini.tsx       |   72.66 |    76.99 |   80.76 |   72.66 | ...1280-1284,1405 
  ...ractiveCli.ts |   86.49 |    80.46 |   87.93 |   86.49 | ...2926,2932,2994 
  ...liCommands.ts |   89.11 |    85.15 |      90 |   89.11 | ...90,507,541,663 
  ...ActiveAuth.ts |     100 |     87.5 |     100 |     100 | 66-80             
 ...cp-integration |   70.24 |     72.9 |   89.96 |   70.24 |                   
  acpAgent.ts      |   69.93 |    72.78 |      90 |   69.93 | ...77,11882-11884 
  authMethods.ts   |      92 |       60 |     100 |      92 | 33-34             
  errorCodes.ts    |       0 |        0 |       0 |       0 | 1-22              
  ...ion-skills.ts |     100 |    88.23 |     100 |     100 | 17,32             
  generation.ts    |    97.1 |    81.25 |     100 |    97.1 | 109,112           
  ...DirContext.ts |     100 |      100 |     100 |     100 |                   
 ...ration/service |   97.04 |    95.71 |   93.33 |   97.04 |                   
  filesystem.ts    |   97.04 |    95.71 |   93.33 |   97.04 | ...21-122,238-239 
 ...ration/session |   90.88 |    86.21 |   96.29 |   90.88 |                   
  Session.ts       |   89.94 |     84.6 |   95.41 |   89.94 | ...81,10308-10312 
  ...entTracker.ts |    96.8 |    89.36 |      90 |    96.8 | 137-143,221       
  ...projection.ts |   98.57 |    93.29 |     100 |   98.57 | ...76,333,344,356 
  ...stop-guard.ts |     100 |    98.07 |     100 |     100 | 37,127            
  ...eplay-page.ts |   93.22 |    90.81 |     100 |   93.22 | 72,83-86,112-122  
  ...y-replayer.ts |   98.53 |    95.52 |     100 |   98.53 | 238-240           
  index.ts         |       0 |        0 |       0 |       0 | 1-40              
  ...ssionUtils.ts |   89.76 |    87.32 |     100 |   89.76 | ...54-270,326-328 
  tasksSnapshot.ts |    94.3 |     87.5 |     100 |    94.3 | 65-71             
  ...on-tracker.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...ssion/emitters |   95.79 |    93.52 |   96.66 |   95.79 |                   
  ...ageEmitter.ts |   95.34 |    94.28 |     100 |   95.34 | 52-59             
  PlanEmitter.ts   |     100 |       90 |     100 |     100 | 66                
  base-emitter.ts  |   78.26 |       75 |     100 |   78.26 | 23-24,26-28       
  index.ts         |       0 |        0 |       0 |       0 | 1-10              
  ...ll-emitter.ts |   99.18 |    96.47 |     100 |   99.18 | 355-356           
 ...ession/rewrite |    91.8 |    89.13 |   94.44 |    91.8 |                   
  LlmRewriter.ts   |    82.4 |     86.2 |     100 |    82.4 | ...,88-89,166-170 
  ...Middleware.ts |   96.96 |    88.09 |     100 |   96.96 | 144,152-154       
  TurnBuffer.ts    |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 src/agent-view    |   89.03 |    81.37 |   89.09 |   89.03 |                   
  ...t-cli-argv.ts |     100 |      100 |     100 |     100 |                   
  protocol.ts      |     100 |      100 |     100 |     100 |                   
  ...sor-client.ts |   80.38 |    72.54 |   76.66 |   80.38 | ...22-626,652-656 
  ...or-process.ts |   96.61 |    89.47 |   84.61 |   96.61 | 129-130,150-151   
  ...sor-runner.ts |    84.9 |     75.6 |      85 |    84.9 | ...44,468,471-481 
  ...sor-server.ts |   85.71 |    83.06 |   95.45 |   85.71 | ...67-468,471-488 
  ...isor-store.ts |   97.73 |    81.16 |     100 |   97.73 | ...92,594,607,643 
  ...nal-bridge.ts |   93.98 |     91.3 |   83.33 |   93.98 | 228-238           
 src/commands      |    89.6 |    72.18 |   64.51 |    89.6 |                   
  auth.ts          |     100 |    83.33 |     100 |     100 | 11,14             
  channel.ts       |   55.55 |      100 |       0 |   55.55 | 18-22,30-40       
  extensions.tsx   |   96.77 |      100 |      50 |   96.77 | 39                
  hooks.tsx        |   66.66 |      100 |       0 |   66.66 | 20-24             
  mcp.ts           |   95.45 |      100 |      50 |   95.45 | 31                
  review.ts        |   98.59 |      100 |      50 |   98.59 | 82                
  serve.ts         |   87.68 |    66.66 |     100 |   87.68 | ...31,743,759-763 
  sessions.ts      |     100 |      100 |      50 |     100 |                   
  update.ts        |   98.13 |    94.44 |   66.66 |   98.13 | 82-83             
 ...mmands/channel |    88.3 |    87.63 |    90.3 |    88.3 |                   
  channel-cwd.ts   |     100 |      100 |     100 |     100 |                   
  ...l-registry.ts |      80 |    84.61 |      80 |      80 | 37-40,49-52,63-66 
  ...entry-path.ts |      75 |       50 |     100 |      75 | 8-9               
  config-utils.ts  |   95.87 |    96.35 |     100 |   95.87 | ...08-213,271-274 
  configure.ts     |    14.7 |      100 |       0 |    14.7 | 18-21,23-84       
  daemon-worker.ts |    93.9 |    85.33 |   94.11 |    93.9 | ...1211,1218-1219 
  loop-runtime.ts  |   91.66 |      100 |      50 |   91.66 | 15,22             
  ...classifier.ts |   98.49 |    96.51 |     100 |   98.49 | 115-116,161       
  ...tact-store.ts |   93.51 |    87.65 |     100 |   93.51 | ...71,288-289,337 
  pairing.ts       |   72.85 |      100 |      50 |   72.85 | 22-28,57-68       
  pidfile.ts       |   95.55 |       90 |     100 |   95.55 | ...50-251,315-316 
  proxy.ts         |     100 |      100 |     100 |     100 |                   
  reload.ts        |    77.5 |    86.95 |      75 |    77.5 | 72-84,93-97       
  runtime.ts       |   82.43 |    86.44 |     100 |   82.43 | ...87-191,251-253 
  set.ts           |   75.72 |    85.71 |      50 |   75.72 | 65-83,111-116     
  start.ts         |    85.8 |    82.17 |      88 |    85.8 | ...85,591-594,606 
  ...ure-format.ts |   93.65 |    82.45 |     100 |   93.65 | ...42,48-49,74-75 
  status.ts        |   78.57 |    59.25 |   66.66 |   78.57 | ...36-137,150-161 
  stop.ts          |   57.83 |    82.35 |      50 |   57.83 | ...3,74-76,85-111 
 ...nds/extensions |   88.82 |    87.64 |   87.09 |   88.82 |                   
  consent.ts       |   72.53 |       90 |   42.85 |   72.53 | ...86-142,157-163 
  disable.ts       |     100 |       90 |     100 |     100 | 30                
  enable.ts        |     100 |    91.66 |     100 |     100 | 38                
  install.ts       |   82.95 |    81.57 |      75 |   82.95 | ...96-199,202-211 
  link.ts          |     100 |      100 |     100 |     100 |                   
  list.ts          |     100 |     87.5 |     100 |     100 | 18                
  new.ts           |     100 |      100 |     100 |     100 |                   
  settings.ts      |   99.15 |      100 |   83.33 |   99.15 | 151               
  sources.ts       |   93.42 |    87.09 |   92.85 |   93.42 | ...4-66,96-98,167 
  uninstall.ts     |   74.57 |       40 |   66.66 |   74.57 | 45-47,60-67,70-73 
  update.ts        |   96.71 |    97.05 |     100 |   96.71 | 114-118           
  utils.ts         |      75 |    53.84 |     100 |      75 | ...27-131,133-137 
 ...les/mcp-server |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-60              
 ...amples/starter |       0 |        0 |       0 |       0 |                   
  example.ts       |       0 |        0 |       0 |       0 | 1-64              
 src/commands/mcp  |   90.17 |    84.39 |   83.33 |   90.17 |                   
  add.ts           |    99.3 |    96.07 |     100 |    99.3 | 154-155           
  approve.ts       |   76.19 |     87.5 |   66.66 |   76.19 | ...,89-99,114-124 
  list.ts          |   92.59 |    83.87 |      80 |   92.59 | ...62-164,180-181 
  reconnect.ts     |   78.85 |    66.66 |   85.71 |   78.85 | 42-55,169-191     
  remove.ts        |     100 |       80 |     100 |     100 | 21-25             
 ...ommands/review |   86.45 |    87.81 |    87.2 |   86.45 |                   
  agent-prompt.ts  |    92.1 |    92.95 |   96.42 |    92.1 | ...1954,2057-2136 
  base-tree.ts     |   76.16 |    80.76 |   77.77 |   76.16 | ...50-371,373-386 
  capture-local.ts |   68.57 |     90.9 |      75 |   68.57 | 107-111,158-189   
  ...k-coverage.ts |   46.92 |    13.33 |   66.66 |   46.92 | ...35-240,253-263 
  cleanup.ts       |   89.12 |    82.22 |   83.33 |   89.12 | ...99-504,506-507 
  ...ent-status.ts |   93.03 |    83.87 |   83.33 |   93.03 | 291,531-551       
  ...ose-review.ts |   96.11 |    91.88 |   95.83 |   96.11 | ...1792,1820-1842 
  cost-ledger.ts   |   94.67 |    95.86 |   78.57 |   94.67 | ...00-501,541-551 
  drive.ts         |   72.22 |    88.88 |   72.72 |   72.22 | ...34-469,473-487 
  extract-step.ts  |   91.36 |    90.62 |   88.88 |   91.36 | ...90-707,714-729 
  fetch-pr.ts      |    76.7 |    68.75 |   63.63 |    76.7 | ...95,417,450-455 
  findings.ts      |   89.35 |    89.13 |   95.45 |   89.35 | ...15-918,927-928 
  load-rules.ts    |   26.41 |      100 |   16.66 |   26.41 | ...41-153,155-156 
  mock-provider.ts |   95.44 |    90.25 |   89.47 |   95.44 | 145,690-709       
  parse-args.ts    |   99.32 |    96.42 |     100 |   99.32 | 400,473           
  plan-diff.ts     |   64.04 |      100 |   66.66 |   64.04 | 127-163           
  pr-context.ts    |   81.77 |    80.86 |   92.85 |   81.77 | ...1043,1072-1074 
  presubmit.ts     |   83.75 |    92.72 |   88.88 |   83.75 | ...77-578,655-685 
  ...ish-assets.ts |   77.24 |    82.75 |   71.42 |   77.24 | ...89-535,537-548 
  ...ve-anchors.ts |   77.77 |    88.88 |      75 |   77.77 | ...77-182,194-211 
  run.ts           |   82.16 |    87.12 |   91.66 |   82.16 | ...52,468-516,529 
  save-artifact.ts |   79.13 |    79.68 |   88.23 |   79.13 | ...50-381,383-386 
  script-lint.ts   |   81.14 |    79.23 |   88.88 |   81.14 | ...59-773,775-797 
  submit.ts        |   83.88 |    83.11 |    90.9 |   83.88 | ...66-470,570-606 
  test-delta.ts    |   87.13 |    91.46 |      75 |   87.13 | 206-237,477-485   
  test-efficacy.ts |   88.04 |    84.12 |   95.45 |   88.04 | ...2602,2610-2630 
  test-plan.ts     |   91.44 |    91.39 |   89.47 |   91.44 | ...38-839,903-920 
 ...w/__fixtures__ |     100 |      100 |     100 |     100 |                   
  ...r-default.mjs |     100 |      100 |     100 |     100 |                   
  ...der-empty.mjs |     100 |      100 |     100 |     100 |                   
  ...der-named.mjs |     100 |      100 |     100 |     100 |                   
 ...nds/review/lib |   96.79 |    94.28 |   97.02 |   96.79 |                   
  agent-briefs.ts  |   98.83 |      100 |       0 |   98.83 | 662-663           
  anchors.ts       |     100 |    94.79 |     100 |     100 | ...33,169,178,225 
  assets.ts        |     100 |      100 |     100 |     100 |                   
  authorization.ts |    92.4 |    92.59 |     100 |    92.4 | 127-133           
  budget.ts        |     100 |      100 |     100 |     100 |                   
  coverage.ts      |   95.73 |    95.21 |   95.45 |   95.73 | ...13,350,459-476 
  deadline.ts      |   97.68 |    91.22 |     100 |   97.68 | 140-141,190,352   
  diff-flags.ts    |     100 |        0 |     100 |     100 | 63                
  diff-plan.ts     |   98.73 |    93.01 |     100 |   98.73 | ...41,264,290-291 
  effort.ts        |     100 |      100 |     100 |     100 |                   
  gh.ts            |   85.92 |    91.11 |   73.33 |   85.92 | ...32,269-270,297 
  git.ts           |   97.64 |    95.65 |     100 |   97.64 | 180-181           
  heavy.ts         |     100 |      100 |     100 |     100 |                   
  inline-counts.ts |     100 |      100 |     100 |     100 |                   
  ledger.ts        |     100 |      100 |     100 |     100 |                   
  local-diff.ts    |    84.4 |    88.46 |     100 |    84.4 | ...63-473,475-483 
  merge-base.ts    |     100 |      100 |     100 |     100 |                   
  path-rules.ts    |     100 |      100 |     100 |     100 |                   
  paths.ts         |     100 |     87.5 |     100 |     100 | 92                
  prompt-record.ts |   95.57 |     87.5 |     100 |   95.57 | ...34,173-174,180 
  receipt.ts       |     100 |      100 |     100 |     100 |                   
  report.ts        |   94.68 |    93.75 |     100 |   94.68 | 187-191           
  retirement.ts    |     100 |    88.75 |     100 |     100 | ...64-265,284,383 
  review-footer.ts |     100 |      100 |     100 |     100 |                   
  roster.ts        |     100 |    94.23 |     100 |     100 | 143,161,206       
  shell-quote.ts   |     100 |      100 |     100 |     100 |                   
  test-utils.ts    |     100 |      100 |     100 |     100 |                   
  transcripts.ts   |   96.49 |    94.44 |     100 |   96.49 | ...98,284-285,309 
  ...pace-scope.ts |     100 |    96.96 |     100 |     100 | 172               
  workspaces.ts    |     100 |     95.9 |     100 |     100 | ...27,452,499,512 
  worktree.ts      |     100 |      100 |     100 |     100 |                   
 ...mands/sessions |   91.56 |    86.95 |   83.33 |   91.56 |                   
  common.ts        |     100 |      100 |     100 |     100 |                   
  list.ts          |   90.96 |    86.66 |   81.81 |   90.96 | 208-219,221-222   
 src/config        |   94.64 |     89.3 |   96.02 |   94.64 |                   
  auth.ts          |   89.35 |    83.56 |     100 |   89.35 | ...97-298,314-315 
  ...eMcpImport.ts |   87.91 |    81.52 |     100 |   87.91 | ...63-371,453-454 
  compile-cache.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   88.93 |    88.61 |   83.78 |   88.93 | ...2456,2458-2466 
  ...cy-monitor.ts |   88.75 |    76.19 |     100 |   88.75 | ...3,90-92,98,101 
  ...ust-policy.ts |   83.04 |    88.49 |     100 |   83.04 | ...39,253,352-353 
  ...heme-names.ts |     100 |      100 |     100 |     100 |                   
  environment.ts   |   94.33 |    89.61 |   94.73 |   94.33 | ...35-639,655-656 
  ...le-watcher.ts |   90.86 |    83.65 |   95.83 |   90.86 | ...23-325,370,418 
  ...resh-state.ts |   90.57 |    97.29 |   93.75 |   90.57 | 137-142,146-152   
  ...ime-reload.ts |     100 |    69.69 |     100 |     100 | ...12-113,122-123 
  hot-reload.ts    |     100 |    89.13 |     100 |     100 | 47,172-178,238    
  keyBindings.ts   |   97.43 |       50 |     100 |   97.43 | 236-239           
  ...ngsAdapter.ts |     100 |    94.11 |     100 |     100 | 64                
  ...ig-watcher.ts |   95.17 |    83.05 |     100 |   95.17 | ...78,200,292-293 
  ...er-secrets.ts |   98.97 |    96.87 |     100 |   98.97 | 85                
  mcpApprovals.ts  |   96.55 |    95.55 |     100 |   96.55 | 223-224,229-231   
  mcpJson.ts       |     100 |      100 |     100 |     100 |                   
  mcpServers.ts    |   92.85 |     87.5 |     100 |   92.85 | 46-47             
  ...idersScope.ts |      95 |    94.73 |     100 |      95 | 11-12             
  ...abledTools.ts |     100 |      100 |     100 |     100 |                   
  ...comparison.ts |     100 |      100 |     100 |     100 |                   
  ...n-settings.ts |   99.15 |    93.75 |     100 |   99.15 | 63                
  sandboxConfig.ts |   93.33 |    93.33 |     100 |   93.33 | ...42-147,216-217 
  ...ings-cache.ts |   98.26 |    97.14 |     100 |   98.26 | 201-202           
  settings.ts      |   90.99 |     92.3 |      90 |   90.99 | ...1006,1008-1009 
  ...ingsSchema.ts |     100 |      100 |     100 |     100 |                   
  ...ngsWatcher.ts |   95.54 |    88.34 |     100 |   95.54 | ...28,277-278,293 
  ...d-env-keys.ts |     100 |      100 |     100 |     100 |                   
  ...l-settings.ts |     100 |      100 |     100 |     100 |                   
  ...paths-lite.ts |   89.47 |       88 |     100 |   89.47 | 43-44,53-54,56-57 
  ...tedFolders.ts |   93.42 |    94.21 |     100 |   93.42 | ...96-397,433-444 
 ...nfig/migration |   95.23 |    77.77 |   83.33 |   95.23 |                   
  index.ts         |   95.65 |     87.5 |     100 |   95.65 | 117-118           
  scheduler.ts     |   96.55 |    77.77 |     100 |   96.55 | 19-20             
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...ation/versions |   94.91 |      100 |     100 |   94.91 |                   
  ...-v2-shared.ts |     100 |      100 |     100 |     100 |                   
  v1-to-v2.ts      |   81.75 |      100 |     100 |   81.75 | ...28-229,231-247 
  v2-to-v3.ts      |     100 |      100 |     100 |     100 |                   
  v3-to-v4.ts      |     100 |      100 |     100 |     100 |                   
  v5-to-v4.ts      |      96 |      100 |     100 |      96 | 94-95,99          
 src/core          |     100 |      100 |     100 |     100 |                   
  auth.ts          |     100 |      100 |     100 |     100 |                   
  initializer.ts   |     100 |      100 |     100 |     100 |                   
  theme.ts         |     100 |      100 |     100 |     100 |                   
 src/dualOutput    |    71.8 |    70.31 |   66.66 |    71.8 |                   
  ...tputBridge.ts |   71.95 |    70.96 |   68.42 |   71.95 | ...08-409,417-420 
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/export        |       0 |        0 |       0 |       0 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-7               
 src/generated     |     100 |      100 |     100 |     100 |                   
  git-commit.ts    |     100 |      100 |     100 |     100 |                   
 src/hooks         |     100 |      100 |     100 |     100 |                   
  ...elete-hook.ts |     100 |      100 |     100 |     100 |                   
 src/i18n          |   85.98 |    81.92 |   89.65 |   85.98 |                   
  index.ts         |   73.45 |    77.77 |      90 |   73.45 | ...70-271,294-299 
  languages.ts     |   93.07 |     92.3 |   85.71 |   93.07 | ...35,164-169,184 
  ...nslateKeys.ts |     100 |      100 |     100 |     100 |                   
  ...lationDict.ts |   93.33 |    66.66 |     100 |   93.33 | 15                
 src/i18n/locales  |     100 |      100 |     100 |     100 |                   
  ca.js            |     100 |      100 |     100 |     100 |                   
  de.js            |     100 |      100 |     100 |     100 |                   
  en.js            |     100 |      100 |     100 |     100 |                   
  fr.js            |     100 |      100 |     100 |     100 |                   
  ja.js            |     100 |      100 |     100 |     100 |                   
  pt.js            |     100 |      100 |     100 |     100 |                   
  ru.js            |     100 |      100 |     100 |     100 |                   
  zh-TW.js         |     100 |      100 |     100 |     100 |                   
  zh.js            |     100 |      100 |     100 |     100 |                   
 ...nonInteractive |      80 |    76.31 |   81.35 |      80 |                   
  session.ts       |   84.08 |    75.27 |   93.61 |   84.08 | ...1007,1016-1026 
  types.ts         |    42.5 |      100 |   33.33 |    42.5 | ...31-632,635-636 
 ...active/control |   75.63 |    89.09 |      80 |   75.63 |                   
  ...rolContext.ts |    6.45 |        0 |       0 |    6.45 | 56-95             
  ...Dispatcher.ts |   91.79 |    92.45 |   88.88 |   91.79 | ...49-367,387,390 
  ...rolService.ts |    6.89 |        0 |       0 |    6.89 | 46-188            
 ...ol/controllers |   42.27 |    64.66 |   48.64 |   42.27 |                   
  ...Controller.ts |   39.49 |      100 |      80 |   39.49 | 88-92,127-210     
  ...Controller.ts |       0 |        0 |       0 |       0 | 1-56              
  ...Controller.ts |   53.96 |    65.71 |   58.33 |   53.96 | ...37-642,644-649 
  ...Controller.ts |   14.06 |      100 |       0 |   14.06 | ...82-117,130-133 
  ...Controller.ts |   37.92 |    60.71 |   46.66 |   37.92 | ...41-653,662-691 
 .../control/types |       0 |        0 |       0 |       0 |                   
  serviceAPIs.ts   |       0 |        0 |       0 |       0 | 1                 
 ...Interactive/io |    98.1 |    94.13 |   95.23 |    98.1 |                   
  ...putAdapter.ts |   97.98 |     93.2 |   98.07 |   97.98 | ...1415,1431-1432 
  ...putAdapter.ts |      96 |    91.66 |   85.71 |      96 | 51-52             
  ...nputReader.ts |     100 |    94.73 |     100 |     100 | 67                
  ...putAdapter.ts |   98.49 |      100 |   90.47 |   98.49 | 85-86,126-127     
  index.ts         |     100 |      100 |     100 |     100 |                   
 src/patches       |       0 |        0 |       0 |       0 |                   
  is-in-ci.ts      |       0 |        0 |       0 |       0 | 1-17              
 src/remoteInput   |   87.31 |    75.32 |   88.23 |   87.31 |                   
  ...utContext.tsx |     100 |      100 |     100 |     100 |                   
  ...putWatcher.ts |   88.01 |       76 |   93.33 |   88.01 | ...49-350,361-364 
  index.ts         |       0 |        0 |       0 |       0 | 1-8               
 src/runtime       |   99.61 |    95.04 |     100 |   99.61 |                   
  ...livery-ipc.ts |     100 |     90.9 |     100 |     100 | 94,106,134        
  ...l-delivery.ts |     100 |      100 |     100 |     100 |                   
  cpu-percent.ts   |     100 |      100 |     100 |     100 |                   
  ...erver-name.ts |     100 |      100 |     100 |     100 |                   
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...-summaries.ts |   86.66 |       50 |     100 |   86.66 | 11,19             
  ...ber-errors.ts |     100 |    95.32 |     100 |     100 | 53,93-94,172,192  
  ...ls-mapping.ts |     100 |      100 |     100 |     100 |                   
 src/serve         |   87.24 |    83.37 |   90.35 |   87.24 |                   
  ...tp-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...ion-bridge.ts |     100 |      100 |     100 |     100 |                   
  auth.ts          |    93.4 |    92.95 |     100 |    93.4 | ...19-320,323-325 
  ...em-adapter.ts |     100 |      100 |     100 |     100 |                   
  capabilities.ts  |     100 |    98.07 |     100 |     100 | 670               
  ...cp-command.ts |     100 |      100 |     100 |     100 |                   
  ...horization.ts |   92.79 |    93.33 |    87.5 |   92.79 | 75-80,135-136     
  ...op-mcp-ipc.ts |   81.06 |    73.68 |   94.11 |   81.06 | ...37-242,267,289 
  ...nt-service.ts |   93.89 |    86.61 |     100 |   93.89 | ...66-468,475,477 
  ...-selection.ts |     100 |      100 |     100 |     100 |                   
  ...ings-store.ts |    86.2 |     92.4 |   95.83 |    86.2 | ...94-206,372-375 
  ...ebhook-ipc.ts |    98.5 |    86.66 |     100 |    98.5 | 47                
  ...iagnostics.ts |     100 |      100 |     100 |     100 |                   
  ...worker-env.ts |     100 |      100 |     100 |     100 |                   
  ...rker-group.ts |   87.27 |     85.2 |     100 |   87.27 | ...10,816-820,838 
  ...er-manager.ts |   89.39 |    83.88 |   93.33 |   89.39 | ...98,711,722-724 
  ...tartup-ipc.ts |   97.72 |    96.66 |     100 |   97.72 | 88-89             
  ...supervisor.ts |   92.42 |    84.44 |    97.1 |   92.42 | ...1462,1516-1520 
  ...e-grouping.ts |     100 |    94.28 |     100 |     100 | 71,137            
  core-runtime.ts  |     100 |      100 |     100 |     100 |                   
  ...ub-session.ts |    90.1 |    77.83 |   94.73 |    90.1 | ...1014,1021-1026 
  daemon-logger.ts |    82.2 |    77.42 |   91.76 |    82.2 | ...1720,1747-1753 
  ...y-pressure.ts |     100 |    96.96 |     100 |     100 | 135               
  ...trics-ring.ts |     100 |      100 |     100 |     100 |                   
  ...s-provider.ts |   68.04 |    52.77 |     100 |   68.04 | ...44-249,282-290 
  daemon-status.ts |   98.57 |     90.8 |     100 |   98.57 | ...1411,1413-1414 
  debug-mode.ts    |     100 |      100 |     100 |     100 |                   
  demo.ts          |     100 |      100 |     100 |     100 |                   
  env-snapshot.ts  |   93.37 |    85.18 |     100 |   93.37 | 114-117,195-202   
  ...-scheduler.ts |   87.34 |    83.87 |     100 |   87.34 | 33-36,48-50,79-81 
  ...d-provider.ts |   92.06 |    86.95 |     100 |   92.06 | ...72,287-293,316 
  ...-path-argv.ts |     100 |      100 |     100 |     100 |                   
  ...h-settings.ts |   94.41 |    88.75 |     100 |   94.41 | ...24,702,718,728 
  fast-path.ts     |   90.98 |    81.38 |   95.45 |   90.98 | ...32-541,607-608 
  ...ration-sse.ts |   42.55 |    33.33 |     100 |   42.55 | 23-24,30,33-56    
  health-query.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-143             
  ...e-observer.ts |   89.89 |    83.24 |      96 |   89.89 | ...11-512,541-543 
  ...back-binds.ts |     100 |    88.88 |     100 |     100 | 32                
  ...-workspace.ts |    90.9 |    85.71 |     100 |    90.9 | ...30-131,142-143 
  ...iders-edit.ts |     100 |    82.14 |     100 |     100 | 58-60,65,81       
  ...ory-picker.ts |     100 |    86.95 |     100 |     100 | 36,66,92          
  ...sion-audit.ts |     100 |      100 |   93.33 |     100 |                   
  rate-limit.ts    |   92.77 |    88.42 |     100 |   92.77 | ...93-295,307-309 
  ...qwen-serve.ts |    83.5 |    79.45 |   75.08 |    83.5 | ...7218,7224-7225 
  ...tup-errors.ts |     100 |      100 |     100 |     100 |                   
  ...-keepalive.ts |   94.19 |     87.5 |     100 |   94.19 | ...26,530-531,571 
  ...-lifecycle.ts |     100 |      100 |     100 |     100 |                   
  server.ts        |   90.26 |    90.87 |   70.75 |   90.26 | ...2660,2674-2678 
  ...on-helpers.ts |     100 |      100 |     100 |     100 |                   
  ...t-event-id.ts |     100 |    95.23 |     100 |     100 | 12                
  ...-admission.ts |   98.71 |    89.65 |     100 |   98.71 | 68                
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...ion-limits.ts |     100 |      100 |     100 |     100 |                   
  ...t-sessions.ts |    93.3 |    76.83 |     100 |    93.3 | ...13,816,829-831 
  ...l-resolver.ts |   90.32 |    66.66 |     100 |   90.32 | 16,45-46          
  ...ell-static.ts |   91.07 |    86.66 |     100 |   91.07 | ...79-182,216-219 
  ...ace-agents.ts |   66.13 |    70.57 |   92.68 |   66.13 | ...2246,2256-2266 
  ...generation.ts |    95.4 |    82.35 |   66.66 |    95.4 | 55-56,78,92       
  ...-git-state.ts |     100 |    91.93 |    90.9 |     100 | 161,172,202,265   
  ...ace-inputs.ts |     100 |      100 |     100 |     100 |                   
  ...ace-memory.ts |      83 |    74.54 |     100 |      83 | ...30-537,597-604 
  ...ers-status.ts |   98.58 |       79 |     100 |   98.58 | 106,134,174,177   
  ...tion-store.ts |   89.67 |    88.27 |   92.59 |   89.67 | ...91-400,411-414 
  ...e-registry.ts |   93.89 |     87.5 |     100 |   93.89 | ...18-519,525-526 
  ...e-remember.ts |   98.23 |    92.51 |     100 |   98.23 | ...36,340-345,386 
  ...te-runtime.ts |   83.98 |    90.19 |     100 |   83.98 | ...48-156,216-237 
  ...me-storage.ts |     100 |      100 |     100 |     100 |                   
  ...management.ts |   72.63 |    72.72 |      96 |   72.63 | ...88-889,896-900 
  ...lls-status.ts |     100 |    95.45 |     100 |     100 | 152               
  ...reconciler.ts |   91.63 |    84.09 |     100 |   91.63 | ...71-273,306-307 
 ...serve/acp-http |   77.23 |    78.75 |   93.33 |   77.23 |                   
  ...r-registry.ts |   96.92 |    94.87 |     100 |   96.92 | 184-187           
  client-mcp-ws.ts |   54.85 |    58.62 |   72.72 |   54.85 | ...99-300,304-305 
  ...n-registry.ts |    98.2 |    88.55 |     100 |    98.2 | 1015,1041-1052    
  dispatch.ts      |   71.95 |    75.11 |   95.55 |   71.95 | ...4897,4945-4951 
  index.ts         |   81.97 |     79.8 |    90.9 |   81.97 | ...2296,2380-2381 
  json-rpc.ts      |     100 |    96.96 |     100 |     100 | 92                
  safe-ws-send.ts  |   52.94 |    71.42 |     100 |   52.94 | 33-42,47-55       
  sse-stream.ts    |   93.96 |    88.57 |   84.61 |   93.96 | ...57-159,161-163 
  ...ort-stream.ts |       0 |        0 |       0 |       0 | 1                 
  ws-stream.ts     |   91.86 |       80 |     100 |   91.86 | 45,50,96,100-103  
 src/serve/auth    |   86.86 |     79.7 |   93.87 |   86.86 |                   
  device-flow.ts   |   96.35 |    80.57 |   97.61 |   96.35 | ...1358,1453,1519 
  ...w-provider.ts |   44.24 |    74.07 |   71.42 |   44.24 | ...23-284,297,301 
 ...rve/cdp-tunnel |   87.73 |    76.21 |    97.5 |   87.73 |                   
  ...r-emulator.ts |   93.27 |    77.77 |     100 |   93.27 | ...53-256,282-283 
  ...verse-link.ts |      88 |    76.19 |     100 |      88 | ...28-329,420-423 
  ...l-registry.ts |     100 |      100 |     100 |     100 |                   
  cdp-ws.ts        |   76.28 |    61.29 |    87.5 |   76.28 | ...13-217,223-228 
 ...nel/acceptance |    6.12 |    57.89 |   46.15 |    6.12 |                   
  ...helpers.d.mts |       0 |        0 |       0 |       0 | 1                 
  ...e-helpers.mjs |   97.64 |    70.96 |     100 |   97.64 | 22-23             
  ...mcp-smoke.mjs |       0 |        0 |       0 |       0 | 1-124             
  ...cceptance.mjs |       0 |        0 |       0 |       0 | 1-473             
  ...re-server.mjs |       0 |        0 |       0 |       0 | 1-59              
  ...ols-smoke.mjs |       0 |        0 |       0 |       0 | 1-268             
  real-tab.mjs     |       0 |        0 |       0 |       0 | 1-218             
  ...al-chrome.mjs |       0 |        0 |       0 |       0 | 1-223             
 src/serve/fs      |   86.39 |    80.74 |     100 |   86.39 |                   
  audit.ts         |     100 |    96.15 |     100 |     100 | 204               
  errors.ts        |     100 |      100 |     100 |     100 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...x-registry.ts |     100 |      100 |     100 |     100 |                   
  paths.ts         |   77.64 |     73.8 |     100 |   77.64 | ...65,594-598,611 
  policy.ts        |   90.42 |    89.18 |     100 |   90.42 | 161-169           
  text-cursor.ts   |   88.23 |       90 |     100 |   88.23 | 74-77,92-95       
  ...ile-system.ts |   86.16 |    79.55 |     100 |   86.16 | ...2510,2520-2521 
 src/serve/live    |   77.28 |    69.21 |   89.91 |   77.28 |                   
  ...en-context.ts |   95.74 |    81.25 |     100 |   95.74 | ...0,66-67,99-100 
  ...-workspace.ts |   88.63 |    82.53 |     100 |   88.63 | ...40-241,253-254 
  discovery.ts     |   85.77 |    76.92 |      90 |   85.77 | ...49-250,255-256 
  ...structions.ts |     100 |      100 |     100 |     100 |                   
  ...oordinator.ts |   82.67 |    76.75 |   97.01 |   82.67 | ...1319,1351-1353 
  ...-installer.ts |   45.17 |    81.96 |   68.18 |   45.17 | ...80-381,395-407 
  ...oordinator.ts |   76.17 |     64.4 |   85.36 |   76.17 | ...1858,1949-1950 
  ...controller.ts |   67.82 |    79.31 |   72.72 |   67.82 | ...66-278,287-295 
  ...ak-to-user.ts |   96.66 |      100 |   83.33 |   96.66 | 37-38             
  ...sk-service.ts |   86.22 |    59.64 |   93.33 |   86.22 | ...1152,1175-1182 
  ...task-tools.ts |      99 |      100 |   85.71 |      99 | 205-206           
  ...redentials.ts |   96.26 |    93.47 |     100 |   96.26 | 91-94             
  ...me-session.ts |   65.63 |    57.24 |   88.88 |   65.63 | ...2270,2275-2282 
  ...up-context.ts |   94.83 |    77.58 |     100 |   94.83 | ...18,327-330,350 
  ...ion-source.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/serve/routes  |   85.22 |     79.5 |   95.15 |   85.22 |                   
  a2ui-action.ts   |   96.84 |     88.5 |    87.5 |   96.84 | ...70-272,309-311 
  capabilities.ts  |     100 |      100 |     100 |     100 |                   
  ...nel-notify.ts |   86.45 |       88 |     100 |   86.45 | ...,83-87,103-104 
  ...l-webhooks.ts |   93.56 |    84.09 |     100 |   93.56 | ...42,292,332,334 
  daemon-status.ts |   85.71 |    83.33 |     100 |   85.71 | 101-108           
  goals.ts         |   98.92 |     90.9 |     100 |   98.92 | 146               
  health-demo.ts   |   94.73 |     86.2 |     100 |   94.73 | 62-66,154         
  live-setup.ts    |   33.33 |     37.5 |      50 |   33.33 | ...18-123,130-135 
  live.ts          |    82.4 |    71.42 |     100 |    82.4 | ...-94,96-101,121 
  permission.ts    |     100 |     92.3 |     100 |     100 | 50,98             
  ...uled-tasks.ts |   87.29 |    82.94 |   92.59 |   87.29 | ...1275,1318-1319 
  ...on-runtime.ts |     100 |    90.47 |     100 |     100 | 58,94             
  session.ts       |   85.42 |    81.81 |   95.31 |   85.42 | ...4768,4770-4771 
  sse-events.ts    |   84.45 |     87.5 |   77.77 |   84.45 | ...36,453-456,485 
  usage-stats.ts   |     100 |    95.45 |     100 |     100 | 118               
  ...space-auth.ts |   85.55 |    75.64 |     100 |   85.55 | ...21-326,331,345 
  ...el-control.ts |   86.26 |    78.94 |     100 |   86.26 | ...17-318,339-347 
  ...management.ts |   90.38 |     78.4 |     100 |   90.38 | ...55-456,475-476 
  ...d-contacts.ts |     100 |      100 |     100 |     100 |                   
  ...controller.ts |   83.09 |       79 |      90 |   83.09 | ...1032,1038,1041 
  ...extensions.ts |   87.23 |    72.76 |   94.11 |   87.23 | ...1826,1871-1872 
  ...-file-read.ts |      91 |    80.91 |     100 |      91 | ...20-621,624-625 
  ...file-write.ts |   84.44 |    64.51 |     100 |   84.44 | ...73-275,355-357 
  ...t-branches.ts |   75.43 |    66.66 |     100 |   75.43 | ...13-618,627-634 
  ...e-git-diff.ts |   97.32 |    90.56 |     100 |   97.32 | 161-162,189-191   
  ...ce-git-log.ts |     100 |    93.18 |     100 |     100 | 52,77,188         
  workspace-git.ts |   77.08 |    89.65 |     100 |   77.08 | 97-118            
  ...github-prs.ts |   88.26 |    63.46 |     100 |   88.26 | ...38-239,264-265 
  ...-lifecycle.ts |   95.23 |    75.75 |     100 |   95.23 | ...50-151,186-187 
  ...management.ts |   87.41 |    84.13 |     100 |   87.41 | ...1660,1680-1685 
  ...cp-control.ts |    73.2 |    67.54 |   85.71 |    73.2 | ...27-633,644-645 
  ...ace-models.ts |   95.53 |    89.74 |     100 |   95.53 | ...52-157,296-297 
  ...ermissions.ts |    77.9 |    72.41 |     100 |    77.9 | ...69-277,298-316 
  ...e-settings.ts |   75.04 |    72.99 |     100 |   75.04 | ...79-690,696-697 
  ...tup-github.ts |   77.97 |    70.58 |   84.21 |   77.97 | ...46-352,397-398 
  ...ace-skills.ts |   69.87 |    78.12 |     100 |   69.87 | ...59-284,290-324 
  ...ace-status.ts |   82.94 |     74.5 |     100 |   82.94 | ...84-486,490-491 
  ...pace-tools.ts |   75.94 |    69.69 |   66.66 |   75.94 | ...59-164,193-194 
  ...pace-trust.ts |   78.92 |    66.21 |      80 |   78.92 | ...38-343,351-352 
  ...pace-voice.ts |   91.33 |    80.92 |     100 |   91.33 | ...70-673,676-678 
 src/serve/server  |   90.72 |    89.11 |   96.55 |   90.72 |                   
  access-log.ts    |   98.68 |     97.1 |     100 |   98.68 | 115,186           
  ...er-helpers.ts |   63.82 |    77.96 |   81.81 |   63.82 | ...16,330,332-347 
  ...w-registry.ts |    98.8 |    81.81 |     100 |    98.8 | 107               
  ...r-handlers.ts |   97.29 |       75 |     100 |   97.29 | 17                
  ...r-response.ts |   85.66 |    76.83 |     100 |   85.66 | ...02,719,782-791 
  fs-factory.ts    |     100 |    92.72 |     100 |     100 | 34,42,103,159     
  ...branch-ops.ts |     100 |      100 |     100 |     100 |                   
  ...t-deadline.ts |     100 |      100 |     100 |     100 |                   
  ...iter-setup.ts |      65 |    73.33 |   33.33 |      65 | 30-35,38-43,47-48 
  ...st-helpers.ts |   95.11 |    95.19 |     100 |   95.11 | ...65-167,422-427 
  self-origin.ts   |   76.19 |       80 |     100 |   76.19 | 45-54             
  ...e-features.ts |      95 |     87.5 |     100 |      95 | 182-188           
  ...on-archive.ts |   89.55 |    87.78 |   97.14 |   89.55 | ...32-836,888-889 
  ...ion-export.ts |     100 |    94.44 |     100 |     100 | 64                
  session-list.ts  |   93.55 |    91.01 |     100 |   93.55 | ...79,681-687,827 
  telemetry.ts     |   99.02 |    97.43 |     100 |   99.02 | ...25,639,781-783 
 src/serve/voice   |   83.35 |    92.22 |   90.47 |   83.35 |                   
  ...ice-config.ts |   84.61 |       30 |     100 |   84.61 | 90-99,103-104     
  voice-ws.ts      |   77.16 |    94.73 |   83.33 |   77.16 | ...68,483,521-523 
  ...oordinator.ts |     100 |    98.21 |     100 |     100 | 176               
 ...kspace-service |   89.11 |    86.15 |   90.69 |   89.11 |                   
  index.ts         |   88.66 |    85.77 |   89.47 |   88.66 | ...1286-1290,1293 
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services      |   92.31 |    88.44 |   97.84 |   92.31 |                   
  ...mandLoader.ts |     100 |    88.88 |     100 |     100 | 105-118           
  ...killLoader.ts |   97.19 |    85.29 |     100 |   97.19 | 142,153-154       
  ...andService.ts |   98.73 |      100 |     100 |   98.73 | 107               
  ...mandLoader.ts |   86.83 |    83.87 |     100 |   86.83 | ...30-335,340-345 
  ...omptLoader.ts |   79.55 |    88.29 |   83.33 |   79.55 | ...48,178,245-246 
  ...mandLoader.ts |   97.77 |    92.15 |     100 |   97.77 | 176,183-184       
  ...nd-factory.ts |   91.42 |    91.66 |     100 |   91.42 | 128,137-144       
  ...ation-tool.ts |     100 |    95.45 |     100 |     100 | 125               
  ...ndMetadata.ts |   98.23 |    96.72 |     100 |   98.23 | 83,87             
  commandUtils.ts  |      96 |     90.9 |     100 |      96 | 48                
  ...and-parser.ts |   90.69 |    85.71 |     100 |   90.69 | 63-66             
  ...ionService.ts |     100 |      100 |     100 |     100 |                   
  prompt-stash.ts  |   96.66 |    92.85 |     100 |   96.66 | 34-35             
  ...tree-lease.ts |   88.23 |    86.48 |     100 |   88.23 | ...94-199,232-233 
  ...low-loader.ts |     100 |    96.15 |     100 |     100 | 88                
  setup-github.ts  |    90.8 |    80.95 |     100 |    90.8 | ...49-450,457-458 
  ...-args-file.ts |   93.93 |    91.66 |    87.5 |   93.93 | 208-210,224-230   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |   98.64 |    95.71 |     100 |   98.64 | 116,142-143       
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  voice-service.ts |   90.37 |    87.87 |     100 |   90.37 | ...80,287,352-357 
  ...e-settings.ts |     100 |    95.23 |     100 |     100 | 19                
  ...ranscriber.ts |   90.46 |    82.19 |      96 |   90.46 | ...66-668,671-673 
 ...rvices/insight |     100 |      100 |     100 |     100 |                   
  dates.ts         |     100 |      100 |     100 |     100 |                   
 ...ght/generators |   88.91 |    86.29 |   96.15 |   88.91 |                   
  DataProcessor.ts |   88.28 |    86.24 |   94.73 |   88.28 | ...1352,1356-1363 
  ...tGenerator.ts |   98.24 |    85.71 |     100 |   98.24 | 47                
  ...teRenderer.ts |     100 |      100 |     100 |     100 |                   
 .../insight/types |       0 |       50 |      50 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 |                   
  ...sightTypes.ts |       0 |        0 |       0 |       0 | 1                 
 ...mpt-processors |   97.27 |    94.04 |     100 |   97.27 |                   
  ...tProcessor.ts |     100 |      100 |     100 |     100 |                   
  ...eProcessor.ts |   94.52 |    84.21 |     100 |   94.52 | 46-47,93-94       
  ...tionParser.ts |     100 |      100 |     100 |     100 |                   
  ...lProcessor.ts |   97.41 |    95.65 |     100 |   97.41 | 95-98             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/services/tips |   97.27 |    84.61 |     100 |   97.27 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  tipHistory.ts    |   92.59 |       70 |     100 |   92.59 | ...24,146,153,162 
  tipRegistry.ts   |     100 |      100 |     100 |     100 |                   
  tipScheduler.ts  |     100 |    91.66 |     100 |     100 | 55                
 src/startup       |   88.99 |    83.47 |    90.9 |   88.99 |                   
  ...p-prefetch.ts |   98.09 |    94.23 |    87.5 |   98.09 | 50,209,225-226    
  ...reeStartup.ts |   80.53 |     74.6 |     100 |   80.53 | ...94,403,409-412 
 src/test-utils    |   94.09 |    79.16 |   77.77 |   94.09 |                   
  ci-env.ts        |      88 |     62.5 |     100 |      88 | 22-23,28          
  ...omMatchers.ts |   69.69 |       50 |      50 |   69.69 | 32-35,37-39,45-47 
  ...mised-lock.ts |     100 |      100 |   66.66 |     100 |                   
  ...andContext.ts |     100 |      100 |     100 |     100 |                   
  render.tsx       |     100 |      100 |     100 |     100 |                   
 src/ui            |   72.71 |    75.23 |    65.9 |   72.71 |                   
  App.tsx          |   33.33 |       75 |   33.33 |   33.33 | 32-86             
  AppContainer.tsx |   73.93 |    72.14 |   70.58 |   73.93 | ...4099,4215-4221 
  ...tionNudge.tsx |    9.58 |      100 |       0 |    9.58 | 24-94             
  ...ackDialog.tsx |    30.3 |      100 |       0 |    30.3 | 26-76             
  ...tionNudge.tsx |    7.69 |      100 |       0 |    7.69 | 25-103            
  colors.ts        |      60 |      100 |   35.29 |      60 | ...52,54-55,60-61 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  keyMatchers.ts   |   95.91 |    97.14 |     100 |   95.91 | 25-26             
  ...tic-colors.ts |     100 |      100 |     100 |     100 |                   
  ...ractiveUI.tsx |   68.12 |    64.86 |   33.33 |   68.12 | ...98,321,341-346 
  ...inePresets.ts |   96.27 |    83.87 |     100 |   96.27 | ...97,402,410-412 
  textConstants.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/auth       |   58.53 |    66.18 |   51.06 |   58.53 |                   
  AuthDialog.tsx   |   59.01 |     42.1 |   16.66 |   59.01 | ...25,332-354,358 
  ...nProgress.tsx |       0 |        0 |       0 |       0 | 1-64              
  ...etupSteps.tsx |   60.21 |    70.73 |   57.69 |   60.21 | ...90,794,803,806 
  useAuth.ts       |    94.6 |    73.52 |     100 |    94.6 | ...21-222,241-247 
  ...rSetupFlow.ts |   43.18 |    33.33 |      50 |   43.18 | ...78-399,416-459 
 src/ui/commands   |    82.5 |    82.83 |   89.12 |    82.5 |                   
  aboutCommand.ts  |     100 |      100 |     100 |     100 |                   
  agentsCommand.ts |   83.78 |      100 |      60 |   83.78 | 30-32,42-44       
  ...odeCommand.ts |    93.1 |    95.23 |     100 |    93.1 | 77-82             
  arenaCommand.ts  |   63.89 |    65.71 |   65.21 |   63.89 | ...01-606,691-699 
  authCommand.ts   |     100 |      100 |     100 |     100 |                   
  branchCommand.ts |     100 |      100 |     100 |     100 |                   
  btwCommand.ts    |   94.32 |    77.41 |     100 |   94.32 | 35-36,114-119     
  bugCommand.ts    |     100 |    77.77 |     100 |     100 | 27,61             
  cdCommand.ts     |    92.3 |    82.75 |     100 |    92.3 | ...,94-99,178,187 
  clearCommand.ts  |    80.9 |    70.83 |     100 |    80.9 | ...24-125,133-142 
  ...essCommand.ts |   68.06 |    54.05 |      75 |   68.06 | ...96-197,211-214 
  ...astCommand.ts |   84.17 |       75 |     100 |   84.17 | ...,91-97,125-130 
  ...ig-command.ts |   93.12 |    88.42 |     100 |   93.12 | ...07-315,321-323 
  ...extCommand.ts |   68.28 |    70.14 |   84.61 |   68.28 | ...66-599,610-611 
  copyCommand.ts   |    98.7 |    96.29 |     100 |    98.7 | 66-67,172,272,323 
  ...or-command.ts |   85.95 |    80.55 |   88.88 |   85.95 | ...68-274,298-309 
  deleteCommand.ts |     100 |      100 |     100 |     100 |                   
  diffCommand.ts   |     100 |    87.87 |     100 |     100 | ...63,231-232,245 
  ...ryCommand.tsx |   81.64 |    87.67 |    90.9 |   81.64 | ...73-278,325-332 
  docsCommand.ts   |     100 |     90.9 |     100 |     100 | 25                
  doctorCommand.ts |   65.37 |    81.88 |   94.11 |   65.37 | ...85-535,538-672 
  dreamCommand.ts  |   85.45 |    88.88 |     100 |   85.45 | 58-65             
  editorCommand.ts |     100 |      100 |     100 |     100 |                   
  ...rt-command.ts |   82.97 |    78.57 |     100 |   82.97 | 47-52,67-70,91-96 
  exportCommand.ts |   98.25 |    91.02 |     100 |   98.25 | ...81,198-199,364 
  ...onsCommand.ts |   52.31 |    56.25 |   69.23 |   52.31 | ...09,277-329,390 
  forgetCommand.ts |     100 |       90 |     100 |     100 | 59                
  forkCommand.ts   |     100 |    94.11 |     100 |     100 | 96,147            
  goalCommand.ts   |   72.81 |    86.84 |   66.66 |   72.81 | ...63-168,277-280 
  helpCommand.ts   |     100 |      100 |     100 |     100 |                   
  ...oryCommand.ts |     100 |      100 |     100 |     100 |                   
  hooksCommand.ts  |   81.13 |    65.71 |   85.71 |   81.13 | ...,86-93,131-132 
  ideCommand.ts    |   60.75 |    64.28 |   41.17 |   60.75 | ...05-306,310-324 
  ...figCommand.ts |   52.83 |    81.25 |      70 |   52.83 | ...74-319,321-330 
  initCommand.ts   |   91.86 |       80 |     100 |   91.86 | 48,83-88          
  ...ghtCommand.ts |   77.87 |    71.42 |     100 |   77.87 | ...44-245,250-272 
  ...ageCommand.ts |   93.45 |    89.06 |     100 |   93.45 | ...68-169,196-206 
  learn-command.ts |     100 |      100 |     100 |     100 |                   
  lspCommand.ts    |     100 |    86.95 |     100 |     100 | 31,101-102        
  mcpCommand.ts    |     100 |      100 |     100 |     100 |                   
  memoryCommand.ts |     100 |      100 |     100 |     100 |                   
  modelCommand.ts  |   84.78 |    82.47 |     100 |   84.78 | ...1071,1105-1110 
  ...onsCommand.ts |     100 |      100 |     100 |     100 |                   
  planCommand.ts   |   78.82 |    76.92 |     100 |   78.82 | 30-35,51-56,68-73 
  quitCommand.ts   |     100 |      100 |     100 |     100 |                   
  recapCommand.ts  |   21.81 |      100 |      50 |   21.81 | 24-73             
  ...ns-command.ts |   98.83 |    81.81 |     100 |   98.83 | 100               
  ...berCommand.ts |     100 |     87.5 |     100 |     100 | 46                
  renameCommand.ts |   89.06 |    88.37 |     100 |   89.06 | ...72-176,202-209 
  ...oreCommand.ts |    90.9 |    86.04 |     100 |    90.9 | ...41-146,176-177 
  resumeCommand.ts |     100 |      100 |     100 |     100 |                   
  rewindCommand.ts |   81.25 |      100 |      50 |   81.25 | 20-22             
  ...ngsCommand.ts |     100 |      100 |     100 |     100 |                   
  ...hubCommand.ts |   89.47 |       75 |      80 |   89.47 | 54-59             
  skillsCommand.ts |   78.82 |    81.81 |     100 |   78.82 | 37-52,78,97       
  statsCommand.ts  |   90.65 |    76.73 |     100 |   90.65 | ...30-733,825-832 
  ...ineCommand.ts |     100 |      100 |     100 |     100 |                   
  ...aryCommand.ts |   73.04 |     82.3 |      90 |   73.04 | ...20-547,561-565 
  tasksCommand.ts  |   77.22 |    72.13 |     100 |   77.22 | ...46-150,172-177 
  ...tupCommand.ts |     100 |      100 |     100 |     100 |                   
  themeCommand.ts  |     100 |      100 |     100 |     100 |                   
  toolsCommand.ts  |     100 |      100 |     100 |     100 |                   
  trustCommand.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
  ...te-command.ts |     100 |    94.11 |     100 |     100 | 74,148            
  vimCommand.ts    |   54.54 |      100 |      50 |   54.54 | 19-29             
  voice-command.ts |   93.57 |       88 |     100 |   93.57 | 35,97-102         
  ...owsCommand.ts |   91.86 |    78.87 |   66.66 |   91.86 | ...60-161,170-175 
 src/ui/components |   71.28 |    78.65 |   79.62 |   71.28 |                   
  AboutBox.tsx     |     100 |      100 |     100 |     100 |                   
  AnsiOutput.tsx   |   65.57 |      100 |      50 |   65.57 | 69-90             
  ApiKeyInput.tsx  |       0 |        0 |       0 |       0 | 1-97              
  AppHeader.tsx    |    88.7 |       75 |     100 |    88.7 | 36,38-43,45       
  ...odeDialog.tsx |   87.24 |    72.22 |   33.33 |   87.24 | ...85,233-238,245 
  AsciiArt.ts      |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |   95.65 |    66.66 |     100 |   95.65 | 27,52             
  ...TextInput.tsx |   88.65 |    90.41 |     100 |   88.65 | ...84-286,300-302 
  Composer.tsx     |   94.49 |    66.66 |     100 |   94.49 | ...-72,84,139,153 
  ...entPrompt.tsx |     100 |      100 |     100 |     100 |                   
  ...ryDisplay.tsx |   75.89 |    62.06 |     100 |   75.89 | ...,88,93-108,113 
  ...geDisplay.tsx |   68.42 |    57.14 |     100 |   68.42 | 16-17,31-32,42-50 
  CronPill.tsx     |     100 |    93.75 |     100 |     100 | 19                
  ...ification.tsx |      84 |       60 |     100 |      84 | 23-24,40-42       
  ...gProfiler.tsx |       0 |        0 |       0 |       0 | 1-36              
  ...ogManager.tsx |       0 |        0 |       0 |       0 | 1-598             
  DiffDialog.tsx   |    53.5 |     37.5 |   69.23 |    53.5 | ...32-737,747-760 
  ...ngsDialog.tsx |       0 |        0 |       0 |       0 | 1-195             
  EffortDialog.tsx |   97.36 |      100 |     100 |   97.36 | 55-56             
  ExitWarning.tsx  |     100 |      100 |     100 |     100 |                   
  ...hProgress.tsx |    87.8 |    33.33 |     100 |    87.8 | 28-31,56          
  ...ustDialog.tsx |     100 |      100 |     100 |     100 |                   
  Footer.tsx       |   74.09 |     61.4 |      50 |   74.09 | ...55-260,278-282 
  ...ngSpinner.tsx |   68.42 |    85.71 |      50 |   68.42 | 35-52,73,80-81    
  GoalPill.tsx     |   93.51 |    81.81 |     100 |   93.51 | 37-38,106-109,123 
  Header.tsx       |   98.65 |    94.73 |     100 |   98.65 | 173,175           
  Help.tsx         |   98.33 |       90 |     100 |   98.33 | ...25,382,448-449 
  ...emDisplay.tsx |   79.06 |    66.33 |     100 |   79.06 | ...04,507,510-516 
  ...ngeDialog.tsx |     100 |      100 |     100 |     100 |                   
  InputPrompt.tsx  |    83.1 |    81.95 |      80 |    83.1 | ...2199,2225,2299 
  ...Shortcuts.tsx |     100 |       88 |     100 |     100 | 98,119            
  ...Indicator.tsx |   98.18 |    97.82 |     100 |   98.18 | 161-162           
  ...firmation.tsx |   91.42 |      100 |      50 |   91.42 | 26-31             
  MainContent.tsx  |    95.9 |    92.53 |      50 |    95.9 | ...99,445-449,452 
  MemoryDialog.tsx |   86.59 |    80.15 |     100 |   86.59 | ...34-435,485,553 
  ...geDisplay.tsx |       0 |        0 |       0 |       0 | 1-41              
  ModelDialog.tsx  |   81.95 |    71.27 |     100 |   81.95 | ...1045,1050-1066 
  ...tsDisplay.tsx |     100 |    97.22 |     100 |     100 | 270               
  ...fications.tsx |       0 |        0 |       0 |       0 | 1-56              
  ...onsDialog.tsx |       0 |        0 |       0 |       0 | 1-1004            
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...icePrompt.tsx |   92.64 |    85.71 |     100 |   92.64 | 102-106,134-139   
  PrepareLabel.tsx |   91.66 |    77.27 |     100 |   91.66 | 73-75,77-79,110   
  ...atePrompt.tsx |       0 |        0 |       0 |       0 | 1-134             
  ...geDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ngDisplay.tsx |       0 |        0 |       0 |       0 | 1-39              
  ...hProgress.tsx |   85.25 |    88.46 |     100 |   85.25 | 121-147           
  ...dSelector.tsx |   92.79 |    82.65 |     100 |   92.79 | ...19-323,354-370 
  ...ionPicker.tsx |   83.66 |    72.13 |     100 |   83.66 | ...96,402,444-466 
  ...onPreview.tsx |   93.58 |    83.78 |     100 |   93.58 | ...,70-71,195-197 
  ...ryDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...putPrompt.tsx |   92.06 |    86.36 |   83.33 |   92.06 | ...,70-72,120-123 
  ...tedDialog.tsx |     100 |      100 |     100 |     100 |                   
  ...ngsDialog.tsx |   71.49 |    73.89 |   69.23 |   71.49 | ...1244,1250-1251 
  ...ionDialog.tsx |    92.3 |    96.15 |   33.33 |    92.3 | 60-63,68-75,164   
  ...putPrompt.tsx |    15.9 |      100 |       0 |    15.9 | 20-63             
  ...Indicator.tsx |   57.14 |      100 |       0 |   57.14 | 12-15             
  ...MoreLines.tsx |       0 |        0 |       0 |       0 | 1-40              
  ...iewDialog.tsx |   97.77 |    87.67 |     100 |   97.77 | ...97,305-307,324 
  ...tsDisplay.tsx |   95.86 |       75 |     100 |   95.86 | 67-71             
  ...ionPicker.tsx |       0 |        0 |       0 |       0 | 1-172             
  ...tivityTab.tsx |    3.94 |      100 |       0 |    3.94 | 27-275            
  StatsDialog.tsx  |    8.64 |      100 |       0 |    8.64 | ...76-111,130-322 
  StatsDisplay.tsx |     100 |      100 |     100 |     100 |                   
  ...ciencyTab.tsx |    78.9 |    56.52 |     100 |    78.9 | ...26,213,262-288 
  ...atmapView.tsx |    8.98 |      100 |       0 |    8.98 | 20-107            
  ...essionTab.tsx |      80 |    66.66 |     100 |      80 | ...70-277,283-300 
  ...ineDialog.tsx |    93.5 |    85.18 |     100 |    93.5 | ...05,267,287-289 
  ...yTodoList.tsx |   96.36 |    88.23 |     100 |   96.36 | 138-141           
  ...nsDisplay.tsx |   92.97 |    83.87 |     100 |   92.97 | ...45,248,275-277 
  ...inalImage.tsx |     100 |     90.9 |     100 |     100 | 75,93             
  ThemeDialog.tsx  |   89.95 |    46.15 |      75 |   89.95 | ...71-173,243-245 
  Tips.tsx         |   93.54 |       75 |     100 |   93.54 | 39-40             
  TodoDisplay.tsx  |     100 |      100 |     100 |     100 |                   
  ...tsDisplay.tsx |     100 |     87.5 |     100 |     100 | 31-32             
  TrustDialog.tsx  |     100 |    83.33 |     100 |     100 | 72-87             
  ...ification.tsx |   36.36 |      100 |       0 |   36.36 | 15-22             
  ...Indicator.tsx |    92.5 |     87.5 |     100 |    92.5 | 50-53             
  ...ackDialog.tsx |       0 |        0 |       0 |       0 | 1-134             
  ...xitDialog.tsx |   80.36 |    43.47 |      60 |   80.36 | ...24-238,248-251 
  ...odeVisuals.ts |   97.22 |    85.71 |     100 |   97.22 | 25                
  ...s-helpers.tsx |   66.25 |    81.25 |      50 |   66.25 | 25-32,46-53,62-72 
 ...nts/agent-view |   55.05 |    69.09 |      50 |   55.05 |                   
  ...atContent.tsx |    9.09 |      100 |       0 |    9.09 | 54-275,281-283    
  ...tChatView.tsx |   21.05 |      100 |       0 |   21.05 | 21-39             
  ...tComposer.tsx |   69.48 |    33.33 |   66.66 |   69.48 | ...51,269,277-279 
  AgentFooter.tsx  |   15.38 |      100 |       0 |   15.38 | 28-65             
  AgentHeader.tsx  |   15.38 |      100 |       0 |   15.38 | 27-64             
  AgentTabBar.tsx  |    87.9 |    63.88 |     100 |    87.9 | ...88,110-118,136 
  ...oryAdapter.ts |     100 |    91.83 |     100 |     100 | 103,109-110,138   
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
 ...mponents/arena |    42.3 |    68.69 |   73.68 |    42.3 |                   
  ArenaCards.tsx   |   73.06 |    71.79 |   85.71 |   73.06 | ...83-185,321-326 
  ...ectDialog.tsx |   83.48 |    69.86 |   88.88 |   83.48 | ...88-392,409-410 
  ...artDialog.tsx |       0 |        0 |       0 |       0 | 1-166             
  ...tusDialog.tsx |       0 |        0 |       0 |       0 | 1-288             
  ...topDialog.tsx |       0 |        0 |       0 |       0 | 1-213             
 ...ackground-view |   82.95 |    81.71 |   92.72 |   82.95 |                   
  ...sksDialog.tsx |   78.73 |    77.65 |   84.61 |   78.73 | ...1809,1833-1839 
  ...TasksPill.tsx |   67.74 |    86.66 |     100 |   67.74 | ...04-124,132-140 
  ...gentPanel.tsx |   97.08 |    86.31 |     100 |   97.08 | 132,442-446,520   
  agent-forest.ts  |    99.2 |    93.93 |     100 |    99.2 | 258               
  ...Visibility.ts |     100 |      100 |     100 |     100 |                   
  ...e-overlay.tsx |    88.2 |    76.47 |     100 |    88.2 | ...36-138,140-142 
 ...nts/extensions |   84.32 |    76.78 |   83.33 |   84.32 |                   
  ...gerDialog.tsx |   82.15 |    76.08 |     100 |   82.15 | ...91-198,258,260 
  TabBar.tsx       |   97.29 |    88.88 |     100 |   97.29 | 33                
  index.ts         |       0 |        0 |       0 |       0 | 1-12              
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...tensions/steps |   46.26 |       85 |   58.82 |   46.26 |                   
  ...ctionStep.tsx |   95.12 |    92.85 |   85.71 |   95.12 | 84-86,89          
  ...etailStep.tsx |       0 |        0 |       0 |       0 | 1-145             
  ...nListStep.tsx |   75.26 |    88.37 |   66.66 |   75.26 | ...53,174,203-209 
  ...electStep.tsx |       0 |        0 |       0 |       0 | 1-83              
  ...nfirmStep.tsx |   16.32 |      100 |       0 |   16.32 | 28-74             
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
 ...xtensions/tabs |   71.92 |    68.21 |   70.83 |   71.92 |                   
  DiscoverTab.tsx  |   68.22 |    67.66 |   55.55 |   68.22 | ...93,656-660,664 
  InstalledTab.tsx |   75.49 |    67.44 |   83.33 |   75.49 | ...77,782-783,820 
  SourcesTab.tsx   |   71.67 |    70.47 |   77.77 |   71.67 | ...28,547,621-633 
 ...tensions/views |   50.97 |    52.38 |   20.83 |   50.97 |                   
  ...tionsView.tsx |   73.75 |    56.36 |   66.66 |   73.75 | ...30,353,369-374 
  ...tionsView.tsx |   43.45 |    44.82 |    6.66 |   43.45 | ...98-405,408-420 
  ...etailView.tsx |    9.56 |      100 |       0 |    9.56 | 40-67,70-158      
 ...mponents/hooks |   87.11 |     81.3 |   91.89 |   87.11 |                   
  ...rListBody.tsx |   95.29 |    85.18 |     100 |   95.29 | 95-98             
  ...etailStep.tsx |   75.32 |    71.42 |      60 |   75.32 | ...56-169,173-186 
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entHeader.tsx |     100 |    85.71 |     100 |     100 | 47                
  ...rListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...abledStep.tsx |     100 |      100 |     100 |     100 |                   
  ...sListStep.tsx |     100 |      100 |     100 |     100 |                   
  ...entDialog.tsx |   72.29 |    70.24 |     100 |   72.29 | ...51,563-568,572 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-13              
  ...erGrouping.ts |     100 |      100 |     100 |     100 |                   
  sourceLabels.ts  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...components/mcp |   40.04 |    61.53 |   70.58 |   40.04 |                   
  ...ealthPill.tsx |   68.42 |    85.71 |     100 |   68.42 | 40-46             
  ...entDialog.tsx |   32.09 |    26.19 |      40 |   32.09 | ...12,914,927-933 
  ...valDialog.tsx |   15.06 |      100 |       0 |   15.06 | 40-109            
  constants.ts     |     100 |      100 |     100 |     100 |                   
  index.ts         |       0 |        0 |       0 |       0 | 1-35              
  types.ts         |     100 |      100 |     100 |     100 |                   
  utils.ts         |      97 |       95 |     100 |      97 | 24,113-114        
 ...ents/mcp/steps |   53.94 |    73.51 |   57.14 |   53.94 |                   
  ...icateStep.tsx |    5.65 |      100 |       0 |    5.65 | 40-66,69-308      
  ...electStep.tsx |   10.95 |      100 |       0 |   10.95 | 16-88             
  ...etailStep.tsx |     100 |      100 |     100 |     100 |                   
  ...eListStep.tsx |   99.09 |    97.36 |     100 |   99.09 | 71                
  ...etailStep.tsx |   62.83 |       60 |   33.33 |   62.83 | ...87-296,307-332 
  ...rListStep.tsx |   88.53 |    81.25 |     100 |   88.53 | ...64,170,175-180 
  ...etailStep.tsx |    10.3 |      100 |       0 |    10.3 | ...1,67-79,82-140 
  ToolListStep.tsx |   69.29 |       50 |     100 |   69.29 | ...23,126,135-144 
 ...nents/messages |   89.93 |    86.42 |   85.29 |   89.93 |                   
  ...ionDialog.tsx |   89.23 |     84.9 |   81.81 |   89.23 | ...75,593,611-613 
  BtwMessage.tsx   |     100 |      100 |     100 |     100 |                   
  ...upDisplay.tsx |     100 |    94.73 |     100 |     100 | ...43,289,402,432 
  ...onMessage.tsx |   92.06 |    82.35 |     100 |   92.06 | 58-60,62,64       
  ...nMessages.tsx |   93.46 |      100 |   76.92 |   93.46 | ...90-292,295-298 
  DiffRenderer.tsx |   93.17 |    86.02 |     100 |   93.17 | ...07,235-236,302 
  ...tsDisplay.tsx |   97.08 |    77.77 |     100 |   97.08 | 95,97,106         
  ...usMessage.tsx |   81.73 |     65.9 |      75 |   81.73 | ...10-214,222,245 
  ...tsDisplay.tsx |   95.52 |    88.31 |     100 |   95.52 | ...40,142,175-180 
  ...ssMessage.tsx |    12.5 |      100 |       0 |    12.5 | 18-59             
  ...edMessage.tsx |   21.05 |      100 |       0 |   21.05 | 23-39             
  ...sMessages.tsx |   59.04 |       50 |    37.5 |   59.04 | ...21-126,147-159 
  ...ryMessage.tsx |   13.63 |      100 |       0 |   13.63 | 23-64             
  ...onMessage.tsx |   91.38 |    81.02 |     100 |   91.38 | ...33-635,642-644 
  ...upMessage.tsx |   98.32 |    95.16 |     100 |   98.32 | 184-187,414       
  ToolMessage.tsx  |   92.62 |    85.29 |   93.33 |   92.62 | ...-982,1009-1011 
 ...ponents/shared |   85.79 |    81.94 |   94.11 |   85.79 |                   
  ...ctionList.tsx |     100 |      100 |      75 |     100 |                   
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  EnumSelector.tsx |     100 |    96.42 |     100 |     100 | 58                
  ...rBoundary.tsx |     100 |      100 |     100 |     100 |                   
  MaxSizedBox.tsx  |   84.71 |    86.86 |      90 |   84.71 | ...67-568,685-686 
  MultiSelect.tsx  |   93.58 |       75 |     100 |   93.58 | ...43,199-201,211 
  ...tonSelect.tsx |     100 |      100 |     100 |     100 |                   
  ...ontroller.tsx |     100 |    83.33 |     100 |     100 | 73,93-95          
  ...eSelector.tsx |     100 |       60 |     100 |     100 | 40-45             
  ...lableList.tsx |   81.48 |    84.84 |     100 |   81.48 | 46-66,73-76       
  StaticRender.tsx |   72.72 |      100 |     100 |   72.72 | 31-33             
  TextInput.tsx    |    80.8 |    67.24 |      80 |    80.8 | ...36-240,252-258 
  ...ontroller.tsx |     100 |    81.81 |     100 |     100 | 59-62             
  ...apsedTime.tsx |     100 |      100 |     100 |     100 |                   
  ...Indicator.tsx |     100 |      100 |     100 |     100 |                   
  ...lizedList.tsx |   88.51 |    83.75 |   81.81 |   88.51 | ...51-779,792,887 
  text-buffer.ts   |   85.98 |    81.81 |   97.91 |   85.98 | ...2664,2762-2763 
  ...er-actions.ts |   73.93 |    67.22 |     100 |   73.93 | ...32-733,934-936 
 ...ponents/skills |       0 |        0 |       0 |       0 |                   
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-681             
 ...ents/subagents |       0 |        0 |       0 |       0 |                   
  constants.ts     |       0 |        0 |       0 |       0 | 1-71              
  index.ts         |       0 |        0 |       0 |       0 | 1-11              
  reducers.tsx     |       0 |        0 |       0 |       0 | 1-190             
  types.ts         |       0 |        0 |       0 |       0 | 1-125             
  utils.ts         |       0 |        0 |       0 |       0 | 1-102             
 ...bagents/create |       0 |        0 |       0 |       0 |                   
  ...ionWizard.tsx |       0 |        0 |       0 |       0 | 1-299             
  ...rSelector.tsx |       0 |        0 |       0 |       0 | 1-85              
  ...onSummary.tsx |       0 |        0 |       0 |       0 | 1-331             
  ...tionInput.tsx |       0 |        0 |       0 |       0 | 1-177             
  ...dSelector.tsx |       0 |        0 |       0 |       0 | 1-63              
  ...nSelector.tsx |       0 |        0 |       0 |       0 | 1-58              
  ...EntryStep.tsx |       0 |        0 |       0 |       0 | 1-78              
  ToolSelector.tsx |       0 |        0 |       0 |       0 | 1-253             
 ...bagents/manage |   14.14 |    53.19 |    37.5 |   14.14 |                   
  ...ctionStep.tsx |       0 |        0 |       0 |       0 | 1-103             
  ...eleteStep.tsx |       0 |        0 |       0 |       0 | 1-62              
  ...tEditStep.tsx |       0 |        0 |       0 |       0 | 1-124             
  ...ctionStep.tsx |   35.61 |    59.52 |     100 |   35.61 | ...21-433,438-440 
  ...iewerStep.tsx |       0 |        0 |       0 |       0 | 1-73              
  ...gerDialog.tsx |       0 |        0 |       0 |       0 | 1-341             
 ...mponents/views |   69.81 |    72.64 |   61.11 |   69.81 |                   
  ContextUsage.tsx |   70.88 |    63.88 |      80 |   70.88 | ...20-426,463-557 
  DoctorReport.tsx |     9.8 |      100 |       0 |     9.8 | 25-54,57-131      
  ...sionsList.tsx |   88.05 |       75 |     100 |   88.05 | 70-77             
  McpStatus.tsx    |   92.01 |     73.8 |     100 |   92.01 | ...36,175-177,262 
  SkillsList.tsx   |   20.51 |      100 |       0 |   20.51 | 17-20,27-57       
  ToolsList.tsx    |     100 |      100 |     100 |     100 |                   
 src/ui/contexts   |   83.96 |    81.62 |    86.3 |   83.96 |                   
  ...ewContext.tsx |   64.83 |    88.88 |      50 |   64.83 | ...16-219,225-235 
  AppContext.tsx   |      80 |       50 |     100 |      80 | 19-20             
  ...ewContext.tsx |   92.45 |    62.79 |      50 |   92.45 | ...69-270,272-276 
  ...igContext.tsx |   81.81 |       50 |     100 |   81.81 | 15-16             
  ...ssContext.tsx |   85.65 |    84.85 |     100 |   85.65 | ...1612-1614,1620 
  ...owContext.tsx |   91.07 |    81.81 |     100 |   91.07 | 47-48,60-62       
  ...deContext.tsx |     100 |      100 |      50 |     100 |                   
  ...onContext.tsx |   80.77 |       80 |    92.3 |   80.77 | ...31-434,443-446 
  ...gsContext.tsx |     100 |      100 |     100 |     100 |                   
  ...usContext.tsx |     100 |      100 |     100 |     100 |                   
  ...ngContext.tsx |   71.42 |       50 |     100 |   71.42 | 17-20             
  ...utContext.tsx |   85.71 |      100 |   66.66 |   85.71 | 13-14             
  ...edContext.tsx |     100 |      100 |      50 |     100 |                   
  ...nsContext.tsx |   88.88 |       50 |     100 |   88.88 | 156-157           
  ...teContext.tsx |   86.66 |       50 |     100 |   86.66 | 235-236           
  ...deContext.tsx |      80 |     87.5 |      75 |      80 | ...11-112,118-120 
  ...rtContext.tsx |     100 |      100 |     100 |     100 |                   
 src/ui/daemon     |   88.35 |    73.51 |   95.45 |   88.35 |                   
  ...ui-adapter.ts |   88.35 |    73.51 |   95.45 |   88.35 | ...74,792-793,879 
 src/ui/editors    |       0 |        0 |       0 |       0 |                   
  ...ngsManager.ts |       0 |        0 |       0 |       0 | 1-67              
 src/ui/hooks      |   84.86 |     82.6 |   87.75 |   84.86 |                   
  ...dProcessor.ts |   85.53 |    85.13 |     100 |   85.53 | ...-970,1017-1018 
  ...ention-ref.ts |   97.72 |       84 |     100 |   97.72 | 65                
  keyToAnsi.ts     |    3.92 |      100 |       0 |    3.92 | 19-77             
  ...esourceRef.ts |     100 |      100 |     100 |     100 |                   
  ...completion.ts |     100 |    95.45 |     100 |     100 | 95                
  ...ention-ref.ts |     100 |      100 |     100 |     100 |                   
  ...dProcessor.ts |   94.62 |    73.58 |     100 |   94.62 | ...86-287,292-293 
  ...dProcessor.ts |   85.63 |    68.16 |   81.81 |   85.63 | ...1452,1473-1477 
  ...rt-command.ts |     100 |      100 |     100 |     100 |                   
  ...sced-flush.ts |     100 |      100 |     100 |     100 |                   
  ...ng-enabled.ts |     100 |      100 |     100 |     100 |                   
  ...oice-input.ts |   92.36 |    81.95 |   66.66 |   92.36 | ...00,502-503,658 
  ...ke-repaint.ts |     100 |      100 |     100 |     100 |                   
  ...amingState.ts |   12.22 |      100 |       0 |   12.22 | 54-157            
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...dScrollbar.ts |     100 |      100 |     100 |     100 |                   
  ...ationFrame.ts |      52 |    63.63 |     100 |      52 | ...59,67-70,76-87 
  ...odeCommand.ts |   58.82 |      100 |     100 |   58.82 | 28,33-48          
  ...enaCommand.ts |      85 |      100 |     100 |      85 | 23-24,29          
  ...aInProcess.ts |   27.92 |       80 |      25 |   27.92 | ...69-170,173-175 
  ...Completion.ts |   86.44 |    88.48 |     100 |   86.44 | ...14-515,525-541 
  ...ifications.ts |   87.82 |    96.77 |     100 |   87.82 | 138-152           
  ...tIndicator.ts |   88.28 |    81.57 |     100 |   88.28 | ...66,175,179-187 
  ...waySummary.ts |   96.26 |       75 |     100 |   96.26 | 126-128,170       
  ...ndTaskView.ts |   94.81 |    76.59 |     100 |   94.81 | 162-166,255,261   
  ...chedScroll.ts |     100 |      100 |     100 |     100 |                   
  ...ketedPaste.ts |    23.8 |      100 |       0 |    23.8 | 19-37             
  ...nchCommand.ts |   95.23 |    82.69 |     100 |   95.23 | ...53-154,277-280 
  ...ompletion.tsx |   97.09 |    87.09 |     100 |   97.09 | ...23-324,334-335 
  ...dMigration.ts |    92.1 |    88.88 |     100 |    92.1 | 42-44             
  useCompletion.ts |   96.29 |    90.56 |     100 |   96.29 | ...17-218,222-223 
  ...nitMessage.ts |     100 |      100 |     100 |     100 |                   
  ...extualTips.ts |   78.26 |       50 |     100 |   78.26 | ...2,75-79,96-104 
  ...eteCommand.ts |   89.52 |    90.69 |     100 |   89.52 | ...98-106,114-115 
  ...ialogClose.ts |   36.11 |       10 |     100 |   36.11 | ...89-195,202-207 
  useDiffData.ts   |       0 |        0 |       0 |       0 | 1-87              
  ...oublePress.ts |   53.12 |       75 |     100 |   53.12 | 33-35,41-54       
  ...orSettings.ts |     100 |      100 |     100 |     100 |                   
  ...Completion.ts |   99.12 |    97.67 |     100 |   99.12 | 182-183           
  ...ionUpdates.ts |   93.72 |    92.98 |     100 |   93.72 | ...87-291,314-320 
  ...agerDialog.ts |   88.88 |      100 |     100 |   88.88 | 21,25             
  ...backDialog.ts |    63.9 |    76.47 |   66.66 |    63.9 | ...66-168,190-191 
  useFocus.ts      |     100 |      100 |     100 |     100 |                   
  ...olderTrust.ts |     100 |    93.33 |     100 |     100 | 62                
  ...ggestions.tsx |   96.47 |    78.94 |     100 |   96.47 | 121,155-156       
  ...miniStream.ts |   83.07 |    80.25 |   74.35 |   83.07 | ...4909-4911,4913 
  ...BranchName.ts |     100 |    94.44 |     100 |     100 | 54                
  ...oryManager.ts |   98.01 |    98.36 |     100 |   98.01 | 139-142           
  ...ooksDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...stListener.ts |     100 |      100 |     100 |     100 |                   
  ...nAuthError.ts |   76.19 |       50 |     100 |   76.19 | 39-40,43-45       
  ...putHistory.ts |   92.59 |    85.71 |     100 |   92.59 | 63-64,72,94-96    
  ...storyStore.ts |     100 |    94.11 |     100 |     100 | 69                
  useKeypress.ts   |     100 |      100 |     100 |     100 |                   
  ...rdProtocol.ts |   36.36 |      100 |       0 |   36.36 | 24-31             
  ...unchEditor.ts |   22.58 |      100 |      50 |   22.58 | 11-32,44-85       
  ...gIndicator.ts |     100 |    96.66 |     100 |     100 | 109               
  useLogger.ts     |      16 |      100 |       0 |      16 | 15-45             
  useMCPHealth.ts  |   63.15 |       80 |      50 |   63.15 | 42-52,64-67       
  ...cpApproval.ts |   93.12 |    86.11 |     100 |   93.12 | ...24-127,139-140 
  useMcpDialog.ts  |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...moryDialog.ts |    87.5 |      100 |     100 |    87.5 | 19,23             
  ...oryMonitor.ts |   83.14 |    78.57 |     100 |   83.14 | 54-63,74-79       
  ...ssageQueue.ts |     100 |     97.4 |     100 |     100 | 175,262           
  ...delCommand.ts |     100 |       96 |     100 |     100 | 61                
  ...ouseEvents.ts |   94.89 |       95 |   83.33 |   94.89 | 78-82             
  ...raseCycler.ts |   84.74 |    76.47 |     100 |   84.74 | ...49,52-53,69-71 
  ...rredEditor.ts |   58.33 |    22.22 |     100 |   58.33 | 23-27,29-33       
  ...derUpdates.ts |    87.4 |    78.78 |     100 |    87.4 | ...71,321-333,381 
  useQwenAuth.ts   |     100 |      100 |     100 |     100 |                   
  ...lScheduler.ts |   89.11 |    87.32 |     100 |   89.11 | ...42-444,476-486 
  ...oryCommand.ts |       0 |        0 |       0 |       0 | 1-7               
  ...umeCommand.ts |   95.18 |    76.47 |     100 |   95.18 | 118-119,220-225   
  ...ompletion.tsx |   90.67 |    83.33 |     100 |   90.67 | ...02,105,138-141 
  ...ectionList.ts |   97.12 |    96.22 |     100 |   97.12 | ...92-193,247-250 
  ...sionPicker.ts |   92.87 |    90.35 |     100 |   92.87 | ...99-501,503-505 
  ...earchInput.ts |     100 |    97.29 |     100 |     100 | 82                
  ...ngsCommand.ts |   18.75 |      100 |       0 |   18.75 | 10-25             
  ...ellHistory.ts |   93.28 |    80.95 |     100 |   93.28 | ...96,153-154,164 
  ...oryCommand.ts |   85.48 |    58.33 |     100 |   85.48 | 22-28,40,71       
  ...agerDialog.ts |   88.23 |      100 |     100 |   88.23 | 20,24             
  ...Completion.ts |   82.85 |    85.13 |   94.73 |   82.85 | ...78-680,688-724 
  ...tateAndRef.ts |     100 |      100 |     100 |     100 |                   
  ...tatsDialog.ts |     100 |      100 |     100 |     100 |                   
  useStatusLine.ts |   97.13 |    93.33 |     100 |   97.13 | ...78-382,478-485 
  ...eateDialog.ts |   88.23 |      100 |     100 |   88.23 | 14,18             
  ...mInProcess.ts |   27.35 |       80 |      25 |   27.35 | ...82-183,186-188 
  ...tification.ts |     100 |     87.5 |     100 |     100 | 50                
  ...alProgress.ts |   67.34 |    58.82 |   66.66 |   67.34 | 52-53,61-68,79-85 
  ...rminalSize.ts |     100 |      100 |     100 |     100 |                   
  ...emeCommand.ts |   67.01 |    29.41 |     100 |   67.01 | ...10-111,115-116 
  useTimer.ts      |   97.59 |    94.73 |     100 |   97.59 | 17-18             
  ...lMigration.ts |       0 |        0 |       0 |       0 |                   
  ...rustModify.ts |     100 |    90.47 |     100 |     100 | 112,134           
  useTurnDiffs.ts  |   95.12 |    78.57 |     100 |   95.12 | 133-134,156-157   
  ...elcomeBack.ts |   87.36 |     90.9 |     100 |   87.36 | ...,94-96,114-115 
  ...reeSession.ts |   93.75 |       70 |     100 |   93.75 | 47-48,72          
  vim.ts           |      74 |    67.56 |   69.23 |      74 | ...1854-1861,1869 
 src/ui/layouts    |    91.2 |    89.47 |     100 |    91.2 |                   
  ...AppLayout.tsx |    90.9 |     87.5 |     100 |    90.9 | 60-62,110-115,151 
  ...AppLayout.tsx |   91.66 |    92.85 |     100 |   91.66 | 75-80             
 src/ui/models     |   80.72 |       80 |   71.42 |   80.72 |                   
  ...ableModels.ts |   80.72 |       80 |   71.42 |   80.72 | ...,61-71,125-127 
 ...noninteractive |     100 |      100 |    6.66 |     100 |                   
  ...eractiveUi.ts |     100 |      100 |    6.66 |     100 |                   
 src/ui/selection  |   86.47 |    79.88 |   96.66 |   86.47 |                   
  screen-buffer.ts |   94.73 |    64.28 |     100 |   94.73 | 51-52             
  ...ion-coords.ts |     100 |      100 |     100 |     100 |                   
  ...ction-span.ts |   92.72 |       90 |     100 |   92.72 | 37-38,67-68       
  ...tion-state.ts |   85.71 |      100 |   88.88 |   85.71 | 51-58             
  ...ction-text.ts |   92.85 |    92.45 |     100 |   92.85 | 30-34,114-115     
  ...selection.tsx |   80.31 |    59.64 |     100 |   80.31 | ...13-314,330-331 
 src/ui/state      |      95 |    81.81 |     100 |      95 |                   
  extensions.ts    |      95 |    81.81 |     100 |      95 | 69-70,89          
 src/ui/themes     |    98.5 |    73.17 |     100 |    98.5 |                   
  ansi-light.ts    |     100 |      100 |     100 |     100 |                   
  ansi.ts          |     100 |      100 |     100 |     100 |                   
  atom-one-dark.ts |     100 |      100 |     100 |     100 |                   
  ayu-light.ts     |     100 |      100 |     100 |     100 |                   
  ayu.ts           |     100 |      100 |     100 |     100 |                   
  color-utils.ts   |   99.23 |    97.05 |     100 |   99.23 | 277-278           
  default-light.ts |     100 |      100 |     100 |     100 |                   
  default.ts       |     100 |      100 |     100 |     100 |                   
  ...inal-theme.ts |   88.59 |    85.96 |     100 |   88.59 | ...57-261,266-270 
  dracula.ts       |     100 |      100 |     100 |     100 |                   
  github-dark.ts   |     100 |      100 |     100 |     100 |                   
  github-light.ts  |     100 |      100 |     100 |     100 |                   
  googlecode.ts    |     100 |      100 |     100 |     100 |                   
  no-color.ts      |     100 |      100 |     100 |     100 |                   
  qwen-dark.ts     |     100 |      100 |     100 |     100 |                   
  qwen-light.ts    |     100 |      100 |     100 |     100 |                   
  ...tic-tokens.ts |     100 |      100 |     100 |     100 |                   
  ...-of-purple.ts |     100 |      100 |     100 |     100 |                   
  theme-manager.ts |   88.68 |    84.52 |     100 |   88.68 | ...83-392,397-398 
  theme.ts         |     100 |    38.02 |     100 |     100 | ...34-449,457-461 
  xcode.ts         |     100 |      100 |     100 |     100 |                   
 src/ui/utils      |   86.74 |    84.95 |   95.48 |   86.74 |                   
  ...Colorizer.tsx |   80.31 |    85.41 |     100 |   80.31 | ...00-201,313-339 
  ...nRenderer.tsx |   79.84 |     75.6 |     100 |   79.84 | ...66,270,328-329 
  ...wnDisplay.tsx |   92.87 |    93.46 |     100 |   92.87 | ...,955,1002-1020 
  ...idDiagram.tsx |   87.79 |    95.34 |     100 |   87.79 | 156-179           
  ...eRenderer.tsx |   92.38 |    81.91 |   95.23 |   92.38 | ...43-746,799-804 
  ...odeDisplay.ts |   94.28 |    85.71 |     100 |   94.28 | 23,40             
  asciiCharts.ts   |    96.7 |     87.5 |     100 |    96.7 | 170-177,278       
  ...dWorkUtils.ts |     100 |      100 |     100 |     100 |                   
  ...boardUtils.ts |   52.52 |    73.25 |   91.66 |   52.52 | ...23,626-635,638 
  commandUtils.ts  |   96.17 |    88.88 |     100 |   96.17 | ...77,179-180,323 
  computeStats.ts  |     100 |      100 |     100 |     100 |                   
  customBanner.ts  |   90.68 |    91.22 |     100 |   90.68 | ...13,324-327,334 
  displayUtils.ts  |   73.84 |    73.91 |     100 |   73.84 | ...34,36-40,42-46 
  formatters.ts    |   94.87 |    98.18 |     100 |   94.87 | 116-119           
  goal-runtime.ts  |   91.17 |    94.73 |     100 |   91.17 | 31-33             
  gradientUtils.ts |     100 |      100 |     100 |     100 |                   
  highlight.ts     |     100 |      100 |     100 |     100 |                   
  ...gap-notice.ts |     100 |      100 |     100 |     100 |                   
  ...oryMapping.ts |     100 |       95 |     100 |     100 | 44,103            
  historyUtils.ts  |   96.03 |     97.1 |     100 |   96.03 | 103-106           
  inline-math.ts   |   98.48 |    95.23 |     100 |   98.48 | 129-130           
  input-mouse.ts   |     100 |    85.71 |     100 |     100 | 48,93             
  isNarrowWidth.ts |     100 |      100 |     100 |     100 |                   
  ...olDetector.ts |   68.81 |       75 |   66.66 |   68.81 | ...27-132,160-161 
  latexRenderer.ts |   94.95 |     73.8 |     100 |   94.95 | ...76-178,184-187 
  layoutUtils.ts   |     100 |      100 |     100 |     100 |                   
  list-mouse.ts    |     100 |      100 |     100 |     100 |                   
  ...ightLoader.ts |     100 |       95 |     100 |     100 | 81                
  ...nUtilities.ts |   98.72 |    94.36 |     100 |   98.72 | 145-146           
  ...t-position.ts |     100 |     87.5 |     100 |     100 | 85                
  ...geRenderer.ts |   86.51 |    70.16 |   95.12 |   86.51 | ...1286,1326-1332 
  ...alRenderer.ts |   86.69 |     71.9 |     100 |   86.69 | ...1476,1513-1519 
  ...lsBySource.ts |     100 |    95.23 |     100 |     100 | 84                
  mouse.ts         |   92.85 |    74.19 |     100 |   92.85 | ...38,145,149-152 
  osc8.ts          |   90.43 |    78.33 |     100 |   90.43 | ...59,244,248-249 
  ...red-height.ts |   98.38 |     97.1 |     100 |   98.38 | 195-197           
  ...mConstants.ts |     100 |      100 |     100 |     100 |                   
  restoreGoal.ts   |     100 |      100 |     100 |     100 |                   
  ...storyUtils.ts |   79.42 |    78.08 |     100 |   79.42 | ...50-572,703-704 
  ...ickerUtils.ts |     100 |      100 |     100 |     100 |                   
  ...evel-label.ts |   77.77 |    66.66 |     100 |   77.77 | 18,22-24          
  ...are-cursor.ts |   89.47 |    85.71 |     100 |   89.47 | 39-44             
  ...ataService.ts |   93.17 |     79.1 |     100 |   93.17 | ...14,227,254-256 
  suggestions.ts   |     100 |      100 |     100 |     100 |                   
  ...izedOutput.ts |   94.94 |      100 |   88.88 |   94.94 | 112-117           
  ...nal-buffer.ts |     100 |      100 |     100 |     100 |                   
  ...e-renderer.ts |   89.19 |    79.54 |     100 |   89.19 | ...14,316-318,434 
  ...wOptimizer.ts |     100 |    96.77 |     100 |     100 | 69                
  terminalSetup.ts |    4.37 |      100 |       0 |    4.37 | 44-393            
  textUtils.ts     |   97.94 |    95.45 |   94.11 |   97.94 | ...82-283,443-444 
  ...background.ts |     100 |      100 |     100 |     100 |                   
  todoSnapshot.ts  |   90.42 |    92.85 |     100 |   90.42 | ...06-207,240-241 
  ...isplay-map.ts |     100 |      100 |     100 |     100 |                   
  updateCheck.ts   |     100 |    92.75 |     100 |     100 | 227-239,331       
  ...ow-keyword.ts |     100 |      100 |     100 |     100 |                   
 ...i/utils/export |   75.03 |     60.3 |   94.59 |   75.03 |                   
  collect.ts       |   71.27 |    66.38 |      96 |   71.27 | ...90-633,655-656 
  index.ts         |     100 |      100 |     100 |     100 |                   
  normalize.ts     |   80.42 |    50.68 |     100 |   80.42 | ...59-364,376-378 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
  utils.ts         |     100 |      100 |     100 |     100 |                   
 ...ort/formatters |   52.92 |    47.22 |   71.42 |   52.92 |                   
  html.ts          |   84.61 |       50 |     100 |   84.61 | ...53,57-58,62-63 
  json.ts          |     100 |      100 |     100 |     100 |                   
  jsonl.ts         |   82.45 |     37.5 |     100 |   82.45 | ...48,50-51,65-66 
  markdown.ts      |   36.32 |    47.05 |      50 |   36.32 | ...16-219,233-295 
 src/ui/voice      |   80.94 |    72.59 |   80.55 |   80.94 |                   
  ...d-recorder.ts |     6.2 |      100 |       0 |     6.2 | ...33-159,162-163 
  ...o-recorder.ts |   84.61 |    93.33 |   57.14 |   84.61 | ...16-117,131-136 
  ...me-session.ts |   89.72 |    65.33 |   93.75 |   89.72 | ...99,305,316-319 
  sox-recorder.ts  |    92.7 |    71.87 |     100 |    92.7 | ...34-135,153-154 
  ...ailability.ts |     100 |      100 |     100 |     100 |                   
  ...e-keyterms.ts |     100 |      100 |     100 |     100 |                   
  voice-model.ts   |     100 |      100 |     100 |     100 |                   
  ...e-recorder.ts |   88.29 |    67.74 |   81.81 |   88.29 | ...,98-99,112,115 
  voice-refine.ts  |     100 |    93.33 |     100 |     100 | 92                
  ...ream-retry.ts |   86.79 |    68.42 |     100 |   86.79 | 16-18,48-49,59-60 
  ...am-session.ts |   88.02 |    66.17 |   84.61 |   88.02 | ...26,343-345,363 
  ...ranscriber.ts |     100 |      100 |     100 |     100 |                   
 src/utils         |    81.4 |       87 |   92.57 |    81.4 |                   
  ...p-profiler.ts |   98.39 |    90.56 |     100 |   98.39 | 141,185,235       
  acpModelUtils.ts |   97.36 |    95.19 |     100 |   97.36 | ...09-210,214-215 
  apiPreconnect.ts |   96.74 |    94.59 |     100 |   96.74 | 167-170           
  ...ol-call-id.ts |   84.61 |       60 |     100 |   84.61 | 26-27,37-38       
  ...ng-failure.ts |     100 |       95 |     100 |     100 | 72                
  checks.ts        |   33.33 |      100 |       0 |   33.33 | 23-28             
  ...-api-error.ts |     100 |    96.42 |     100 |     100 | 14                
  cleanup.ts       |   84.05 |    94.11 |      80 |   84.05 | 80,111-121        
  commands.ts      |   97.45 |    96.66 |     100 |   97.45 | 153-155           
  ...Calculator.ts |     100 |      100 |     100 |     100 |                   
  cpuProfiler.ts   |   70.73 |    73.23 |   88.88 |   70.73 | ...27,430-431,438 
  deepMerge.ts     |     100 |       90 |     100 |     100 | 41-43,49          
  ...re-runtime.ts |     100 |      100 |     100 |     100 |                   
  ...ScopeUtils.ts |   97.56 |    88.88 |     100 |   97.56 | 67                
  doctorChecks.ts  |   70.31 |    74.57 |     100 |   70.31 | ...95-301,325-341 
  ...putCapture.ts |   90.65 |    86.31 |     100 |   90.65 | ...73,371,373-374 
  ...arResolver.ts |   97.14 |    96.55 |     100 |   97.14 | 125-126           
  errors.ts        |   97.56 |    94.64 |     100 |   97.56 | 69-70,304-305     
  events.ts        |     100 |      100 |     100 |     100 |                   
  ...on-mention.ts |   88.48 |     82.6 |     100 |   88.48 | ...56-160,164-168 
  gitUtils.ts      |   92.85 |    86.66 |     100 |   92.85 | ...13-116,164-167 
  ...AutoUpdate.ts |    93.1 |       94 |      90 |    93.1 | 103,108,179-190   
  ...tyWarnings.ts |     100 |      100 |     100 |     100 |                   
  ...lationInfo.ts |   97.68 |    94.28 |     100 |   97.68 | ...64,381-382,427 
  jsonc-editor.ts  |   93.18 |    92.72 |     100 |   93.18 | ...80-381,384-385 
  languageUtils.ts |   98.88 |    97.05 |     100 |   98.88 | 184-185           
  load-undici.ts   |     100 |      100 |     100 |     100 |                   
  ...npm-update.ts |   86.64 |    77.02 |     100 |   86.64 | ...03-304,335-345 
  math.ts          |       0 |        0 |       0 |       0 | 1-15              
  ...er-mention.ts |     100 |    66.66 |     100 |     100 | 14,30,44-46       
  ...iagnostics.ts |   94.57 |    83.01 |   88.88 |   94.57 | ...05,311,315-317 
  ...serMessage.ts |     100 |      100 |     100 |     100 |                   
  ...onfigUtils.ts |   94.25 |    91.17 |     100 |   94.25 | ...30,436,439-443 
  ...iveHelpers.ts |   95.13 |    91.79 |     100 |   95.13 | ...53-454,552,565 
  osc.ts           |   97.18 |      100 |    87.5 |   97.18 | 182-183           
  package.ts       |   88.88 |    85.71 |     100 |   88.88 | 31-32             
  ...uggestions.ts |   84.29 |    70.83 |     100 |   84.29 | 70-76,92-103      
  processUtils.ts  |    92.3 |       80 |     100 |    92.3 | 45-46             
  readStdin.ts     |   93.67 |    94.11 |   85.71 |   93.67 | 79-83             
  relaunch.ts      |   95.87 |    89.28 |     100 |   95.87 | 103-105,131       
  resolvePath.ts   |     100 |      100 |     100 |     100 |                   
  runBudget.ts     |   99.35 |    96.77 |     100 |   99.35 | 119               
  sandbox-path.ts  |     100 |      100 |     100 |     100 |                   
  sandbox.ts       |   45.67 |    56.93 |   76.92 |   45.67 | ...1034,1046-1069 
  ...xImageName.ts |     100 |    77.77 |     100 |     100 | 10,18             
  sandboxMounts.ts |     100 |      100 |     100 |     100 |                   
  sessionPaths.ts  |   90.84 |    90.56 |     100 |   90.84 | ...81-182,185-186 
  settingsUtils.ts |   82.35 |    89.57 |      90 |   82.35 | ...25-743,750-758 
  spawnWrapper.ts  |     100 |      100 |     100 |     100 |                   
  ...ate-verify.ts |     100 |      100 |     100 |     100 |                   
  ...one-update.ts |   39.81 |    77.44 |   62.16 |   39.81 | ...1193,1196-1215 
  ...upProfiler.ts |   98.47 |    94.66 |     100 |   98.47 | 132-133,308       
  ...upWarnings.ts |     100 |      100 |     100 |     100 |                   
  stdioHelpers.ts  |     100 |       90 |     100 |     100 | 23                
  systemInfo.ts    |   95.12 |    90.27 |     100 |   95.12 | ...54-255,260-264 
  ...InfoFields.ts |    87.5 |    65.85 |     100 |    87.5 | ...24-125,146-147 
  ...alSequence.ts |     100 |    97.61 |     100 |     100 | 60                
  ...iffPreview.ts |   76.47 |       25 |     100 |   76.47 | 13,17,23-24       
  ...on-handler.ts |    73.8 |       75 |     100 |    73.8 | 17-18,25-26,67-73 
  ...e-relaunch.ts |   89.61 |    86.66 |      50 |   89.61 | 56-61,83-84       
  ...entEmitter.ts |     100 |      100 |     100 |     100 |                   
  ...ansionHook.ts |     100 |      100 |     100 |     100 |                   
  ...upWarnings.ts |   87.75 |       75 |     100 |   87.75 | 47-48,53-54,57-58 
  version.ts       |     100 |    66.66 |     100 |     100 | 11                
  ...ingHandler.ts |     100 |      100 |     100 |     100 |                   
  windowTitle.ts   |   95.45 |    93.33 |     100 |   95.45 | 54-55             
  ...WithBackup.ts |   65.04 |    77.77 |     100 |   65.04 | 97,112,133-172    
 ...s/housekeeping |   91.63 |    91.02 |      95 |   91.63 |                   
  cleanup.ts       |   95.77 |    95.83 |     100 |   95.77 | 70-72             
  ...eractionAt.ts |     100 |      100 |     100 |     100 |                   
  scheduler.ts     |   91.91 |    90.47 |    87.5 |   91.91 | 58-62,73,131-135  
  throttledOnce.ts |   86.66 |     86.2 |     100 |   86.66 | ...99,105,137-138 
-------------------|---------|----------|---------|---------|-------------------
Core Package - Full Text Report
-------------------|---------|----------|---------|---------|-------------------
File               | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------------|---------|----------|---------|---------|-------------------
All files          |   87.67 |    86.24 |   89.27 |   87.67 |                   
 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.46 |    84.13 |   95.65 |   90.46 |                   
  ...transcript.ts |   87.63 |    83.52 |     100 |   87.63 | ...80,588,594-598 
  ...ent-resume.ts |   85.59 |    77.55 |   83.33 |   85.59 | ...1793-1797,1800 
  ...ound-tasks.ts |   96.15 |    90.13 |   98.76 |   96.15 | ...1732,1752-1755 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...ent-result.ts |    96.8 |    92.68 |     100 |    96.8 | 106,129-131       
  ...n-registry.ts |    93.3 |    86.11 |     100 |    93.3 | ...85-991,996-998 
  ...w-snapshot.ts |   91.86 |    75.75 |     100 |   91.86 | ...54,178,185-187 
 src/agents/arena  |   76.32 |    67.71 |   78.94 |   76.32 |                   
  ...gentClient.ts |   79.47 |    88.88 |   81.81 |   79.47 | ...68-183,189-204 
  ArenaManager.ts  |   75.11 |    64.51 |   78.57 |   75.11 | ...1887,1893-1894 
  arena-events.ts  |   64.44 |      100 |      50 |   64.44 | ...71-175,178-183 
  diff-summary.ts  |    87.5 |    72.34 |     100 |    87.5 | ...32-133,137-138 
  index.ts         |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...gents/backends |   78.09 |    85.23 |   76.28 |   78.09 |                   
  ITermBackend.ts  |   97.97 |    93.93 |     100 |   97.97 | ...78-180,255,307 
  ...essBackend.ts |    90.9 |    85.36 |   93.33 |    90.9 | ...70,672,674-675 
  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 |   90.55 |    86.14 |   87.64 |   90.55 |                   
  agent-context.ts |     100 |      100 |     100 |     100 |                   
  agent-core.ts    |   85.07 |     76.8 |   77.77 |   85.07 | ...2291,2337-2339 
  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.34 |      100 |    92.3 |   98.34 | 81-82             
  ...low-budget.ts |     100 |      100 |     100 |     100 |                   
  ...ow-journal.ts |   91.76 |    75.86 |     100 |   91.76 | ...38-139,179-181 
  ...chestrator.ts |   91.86 |    88.71 |   82.35 |   91.86 | ...1782,1831-1834 
  ...ow-prompts.ts |     100 |      100 |     100 |     100 |                   
  ...low-runner.ts |    94.3 |    87.17 |   91.66 |    94.3 | ...74,222,242-245 
  ...ow-sandbox.ts |   96.87 |    94.64 |     100 |   96.87 | ...24-325,330-331 
  ...flow-saved.ts |   96.51 |    94.36 |     100 |   96.51 | 134-135,234-237   
  ...flow-stall.ts |    97.9 |    83.33 |     100 |    97.9 | 138-139,236       
 src/agents/tasks  |     100 |      100 |     100 |     100 |                   
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/agents/team   |   82.04 |    84.17 |   88.97 |   82.04 |                   
  TeamManager.ts   |   72.02 |    79.41 |   79.24 |   72.02 | ...1632,1655-1656 
  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.24 |    82.82 |     100 |   89.24 | ...-994,1038-1039 
  team-events.ts   |   60.52 |      100 |      50 |   60.52 | ...40-144,151-155 
  teamHelpers.ts   |   92.02 |    94.91 |   95.23 |   92.02 | ...31-332,368-378 
  types.ts         |     100 |      100 |     100 |     100 |                   
 ...eam/test-utils |   94.39 |    94.26 |   98.21 |   94.39 |                   
  ...on-harness.ts |   96.49 |    84.21 |     100 |   96.49 | 128-129,141-142   
  fake-agent.ts    |   98.49 |    95.08 |     100 |   98.49 | 201-203           
  fake-backend.ts  |   86.46 |    97.61 |   95.83 |   86.46 | 124-146           
 src/config        |   84.97 |    87.13 |   75.37 |   84.97 |                   
  approval-mode.ts |     100 |      100 |     100 |     100 |                   
  ...xtDefaults.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |   84.29 |    86.85 |   73.79 |   84.29 | ...8346,8350-8351 
  constants.ts     |     100 |      100 |     100 |     100 |                   
  models.ts        |     100 |      100 |     100 |     100 |                   
  storage.ts       |   94.39 |    91.57 |   88.23 |   94.39 | ...45-446,449-450 
 ...nfirmation-bus |   98.27 |    97.14 |     100 |   98.27 |                   
  message-bus.ts   |   98.14 |    97.05 |     100 |   98.14 | 42-43             
  types.ts         |     100 |      100 |     100 |     100 |                   
 src/core          |   91.95 |    87.94 |   93.23 |   91.95 |                   
  baseLlmClient.ts |   88.37 |    83.68 |   81.81 |   88.37 | ...51,664,670-672 
  client.ts        |   91.91 |    87.15 |   91.56 |   91.91 | ...3922,4016-4017 
  ...tGenerator.ts |   86.34 |    87.34 |   84.61 |   86.34 | ...81-482,527-533 
  ...lScheduler.ts |   90.05 |    84.69 |   96.19 |   90.05 | ...6225,6253-6269 
  geminiChat.ts    |   92.97 |    89.89 |   95.41 |   92.97 | ...4938,4984-4985 
  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 | 49-50             
  ...on-helpers.ts |   93.49 |    78.57 |     100 |   93.49 | ...10-211,228-229 
  ...issionFlow.ts |   98.97 |    96.96 |     100 |   98.97 | 107               
  ...try-policy.ts |     100 |      100 |     100 |     100 |                   
  ...ell-policy.ts |   95.19 |    89.47 |     100 |   95.19 | ...44-245,290-291 
  prompts.ts       |   93.64 |    91.42 |   83.33 |   93.64 | ...1208,1411-1412 
  ...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        
  ...reparation.ts |     100 |      100 |     100 |     100 |                   
  ...tion-guard.ts |   90.38 |    94.73 |     100 |   90.38 | 68-72             
  ...allIdUtils.ts |   98.41 |    93.47 |     100 |   98.41 | 36,45             
  ...okTriggers.ts |   99.45 |    92.43 |     100 |   99.45 | 182,193           
  ...terruption.ts |     100 |     92.3 |     100 |     100 | 86,104            
  turn.ts          |   98.53 |    91.74 |     100 |   98.53 | ...25,653-654,701 
  ...l-fallback.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   96.33 |    88.12 |   96.15 |   96.33 |                   
  ...tGenerator.ts |   97.24 |    86.72 |   94.87 |   97.24 | ...1429,1458,1469 
  converter.ts     |   96.19 |    89.25 |     100 |   96.19 | ...1329,1550-1552 
  index.ts         |       0 |        0 |       0 |       0 | 1-21              
  usage.ts         |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   88.78 |    72.36 |   89.47 |   88.78 |                   
  ...tGenerator.ts |   87.18 |    71.83 |   88.88 |   87.18 | ...58-364,382-383 
  index.ts         |     100 |       80 |     100 |     100 | 50                
 ...ntentGenerator |    95.6 |    88.74 |    92.3 |    95.6 |                   
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tGenerator.ts |   95.52 |    87.88 |   91.89 |   95.52 | ...1195-1196,1224 
  ...tDetection.ts |     100 |      100 |     100 |     100 |                   
 ...ntentGenerator |   91.71 |    90.27 |   95.28 |   91.71 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  converter.ts     |   91.15 |    89.32 |   96.87 |   91.15 | ...1914,2083-2098 
  errorHandler.ts  |     100 |      100 |     100 |     100 |                   
  index.ts         |   60.31 |       75 |      50 |   60.31 | ...71,74-78,90-94 
  ...tGenerator.ts |    66.4 |    70.58 |   88.88 |    66.4 | ...51-157,168-169 
  pipeline.ts      |   96.69 |    91.07 |     100 |   96.69 | ...1125,1133,1232 
  ...ix-caching.ts |     100 |      100 |     100 |     100 |                   
  ...ureContext.ts |     100 |      100 |     100 |     100 |                   
  ...ingOptions.ts |       0 |        0 |       0 |       0 | 1                 
  ...CallParser.ts |   92.24 |     92.4 |     100 |   92.24 | ...28-529,549-552 
  ...kingParser.ts |     100 |    96.87 |     100 |     100 | 42                
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...rator/provider |   97.19 |    90.47 |   98.36 |   97.19 |                   
  dashscope.ts     |   98.36 |    93.03 |   95.65 |   98.36 | ...93-494,636-637 
  deepseek.ts      |   94.91 |    89.36 |     100 |   94.91 | ...31-132,145-146 
  default.ts       |   99.16 |    96.96 |     100 |   99.16 | 198               
  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 |                   
  types.ts         |       0 |        0 |       0 |       0 |                   
  utils.ts         |     100 |      100 |     100 |     100 |                   
  zai.ts           |   92.13 |    82.14 |     100 |   92.13 | ...,39-40,135-137 
 src/extension     |   86.21 |    83.25 |   92.35 |   86.21 |                   
  ...ive-safety.ts |     100 |      100 |     100 |     100 |                   
  ...-converter.ts |   78.32 |    71.83 |     100 |   78.32 | ...1122,1168-1169 
  corruptFile.ts   |     100 |       50 |     100 |     100 | 40-45             
  ...-converter.ts |   80.39 |     87.5 |     100 |   80.39 | 50-59             
  ...me-refresh.ts |     100 |      100 |     100 |     100 |                   
  ...sion-store.ts |   90.85 |    86.38 |   97.87 |   90.85 | ...1218-1224,1268 
  ...ionManager.ts |   81.06 |    78.78 |   81.52 |   81.06 | ...2705,2727-2728 
  ...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 |    84.61 |   85.71 |    75.9 | ...98,202,214-248 
  github.ts        |   88.58 |    82.13 |     100 |   88.58 | ...62,952-953,963 
  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 |     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.33 |     100 |   94.11 | 63-64,81-82       
  redaction.ts     |     100 |      100 |     100 |     100 |                   
  settings.ts      |   66.26 |      100 |      50 |   66.26 | 81-107,141-146    
  ...ceRegistry.ts |   94.01 |    83.14 |     100 |   94.01 | ...38-344,365-366 
  storage.ts       |     100 |      100 |     100 |     100 |                   
  ...ableSchema.ts |     100 |      100 |     100 |     100 |                   
  variables.ts     |   88.95 |    83.78 |     100 |   88.95 | ...32-235,238-241 
  ...extraction.ts |   85.77 |    80.61 |   89.47 |   85.77 | ...02-205,260-261 
 src/followup      |   79.92 |    80.07 |    90.9 |   79.92 |                   
  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   |   71.64 |    65.38 |   71.42 |   71.64 | ...52-653,660-661 
  ...onToolGate.ts |     100 |    96.55 |     100 |     100 | 97                
  ...nGenerator.ts |   72.03 |    81.15 |   83.33 |   72.03 | ...68-219,331-333 
 src/generated     |       0 |        0 |       0 |       0 |                   
  git-commit.ts    |       0 |        0 |       0 |       0 | 1-10              
 src/goals         |   94.22 |    88.93 |    95.9 |   94.22 |                   
  ...eGoalStore.ts |   87.61 |    88.88 |   86.66 |   87.61 | ...85-188,196-204 
  goal-evidence.ts |   87.61 |    85.02 |   95.65 |   87.61 | ...13-614,637-640 
  ...projection.ts |   89.41 |    72.22 |   66.66 |   89.41 | ...28,131,135-137 
  ...ersistence.ts |   87.73 |    84.84 |      80 |   87.73 | ...-94,97,101-106 
  goal-protocol.ts |      92 |       90 |     100 |      92 | 109-110           
  goal-reducer.ts  |   92.45 |    85.93 |     100 |   92.45 | ...84-385,398,453 
  goal-runtime.ts  |   99.05 |    93.64 |     100 |   99.05 | ...20-721,744-745 
  goal-tools.ts    |   98.22 |    93.02 |      95 |   98.22 | ...46-147,248-249 
  ...rn-context.ts |     100 |      100 |     100 |     100 |                   
  goal-verifier.ts |   92.46 |    92.85 |     100 |   92.46 | ...69-172,185-187 
  goal-wire.ts     |       0 |        0 |       0 |       0 | 1-27              
  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.11 |    86.37 |   88.62 |   88.11 |                   
  ...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 |    89.13 |     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.57 |   66.14 |   58.96 |                   
  ...nfigLoader.ts |   80.55 |       72 |   95.45 |   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 |       80 |   16.66 |   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.19 |   89.28 |   82.12 |                   
  ...en-storage.ts |     100 |      100 |     100 |     100 |                   
  ...en-storage.ts |   87.08 |    87.03 |   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        |   87.61 |    83.61 |   90.53 |   87.61 |                   
  ...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 
  const.ts         |   94.28 |     92.3 |     100 |   94.28 | 66-67             
  dream.ts         |    64.6 |    72.22 |      50 |    64.6 | ...04-109,124-165 
  ...entPlanner.ts |     100 |    83.33 |     100 |     100 | 136,146           
  entries.ts       |   75.59 |    84.84 |   83.33 |   75.59 | ...56-157,172-180 
  extract.ts       |   91.48 |    75.75 |     100 |   91.48 | ...99,118-121,189 
  ...entPlanner.ts |   91.59 |    76.74 |     100 |   91.59 | ...05,114-117,293 
  ...ionPlanner.ts |       0 |        0 |       0 |       0 | 1                 
  forget.ts        |   81.83 |       75 |   83.33 |   81.83 | ...51,474,478-507 
  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.4 |    82.29 |   77.77 |    78.4 | ...1482,1495-1497 
  ...ent-config.ts |   86.95 |    82.52 |   86.36 |   86.95 | ...68,388,395-401 
  memoryAge.ts     |   90.47 |       80 |     100 |   90.47 | 50-51             
  paths.ts         |   95.29 |    96.59 |     100 |   95.29 | ...80-381,402-403 
  ...ing-skills.ts |     100 |       72 |     100 |     100 | 31-35,73-78,97    
  prompt.ts        |   97.26 |    87.03 |     100 |   97.26 | ...10-218,222,225 
  recall.ts        |   82.06 |       75 |    90.9 |   82.06 | ...59-364,395-406 
  refresh.ts       |   89.85 |    82.92 |     100 |   89.85 | ...54-155,162-163 
  ...ceSelector.ts |    93.1 |    81.81 |     100 |    93.1 | ...25,127-128,136 
  remember.ts      |   98.89 |    90.19 |     100 |   98.89 | 50,70             
  scan.ts          |   93.12 |    77.41 |     100 |   93.12 | ...08-109,154,157 
  scopes.ts        |     100 |      100 |     100 |     100 |                   
  ...et-scanner.ts |     100 |      100 |     100 |     100 |                   
  ...entPlanner.ts |   77.24 |    74.07 |   72.22 |   77.24 | ...52-456,459,465 
  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 |     87.5 |     100 |     100 | 30                
  ...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 |    81.53 |   81.81 |   81.21 | ...63-277,291-296 
 src/mocks         |       0 |        0 |       0 |       0 |                   
  msw.ts           |       0 |        0 |       0 |       0 | 1-9               
 src/models        |   92.55 |    88.97 |   91.13 |   92.55 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...tor-config.ts |   97.77 |    91.83 |     100 |   97.77 | 155,161,171       
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...nfigErrors.ts |   74.22 |    47.82 |   84.61 |   74.22 | ...,67-74,106-117 
  ...igResolver.ts |   98.71 |    93.33 |     100 |   98.71 | 166,328,334       
  modelRegistry.ts |     100 |    98.11 |     100 |     100 | 177,261           
  modelsConfig.ts  |   89.36 |    86.93 |   88.09 |   89.36 | ...1404,1433-1434 
  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.77 |    91.26 |   71.07 |   83.77 |                   
  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.54 |    89.63 |      80 |   86.54 | ...1096,1202-1206 
  rule-parser.ts   |   94.49 |     92.7 |     100 |   94.49 | ...1447,1481-1483 
  ...-semantics.ts |   70.44 |    91.07 |   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.71 |     78.5 |   81.25 |   83.71 |                   
  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.85 |    73.84 |   78.26 |   75.85 | ...73-474,502-503 
  types.ts         |       0 |        0 |       0 |       0 | 1                 
 ...viders/presets |   97.82 |    91.66 |   63.63 |   97.82 |                   
  ...oding-plan.ts |   87.34 |      100 |       0 |   87.34 | 82-84,87-89,91-94 
  ...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 |                   
  openrouter.ts    |     100 |      100 |     100 |     100 |                   
  requesty.ts      |     100 |      100 |     100 |     100 |                   
  zai.ts           |     100 |      100 |     100 |     100 |                   
 src/qwen          |   85.41 |    78.52 |   95.89 |   85.41 |                   
  ...tGenerator.ts |   98.64 |    98.18 |     100 |   98.64 | 105-106           
  qwenOAuth2.ts    |   82.79 |    73.29 |   90.62 |   82.79 | ...1205-1221,1251 
  ...kenManager.ts |   85.36 |    76.61 |     100 |   85.36 | ...52-757,778-783 
 src/resources     |     100 |      100 |     100 |     100 |                   
  ...e-registry.ts |     100 |      100 |     100 |     100 |                   
 src/services      |   89.62 |    84.47 |   96.85 |   89.62 |                   
  ...ionTrailer.ts |     100 |      100 |     100 |     100 |                   
  ...llRegistry.ts |   97.68 |    85.71 |     100 |   97.68 | ...96,119,490-491 
  ...ionService.ts |   97.45 |    95.45 |     100 |   97.45 | ...,905,1048-1056 
  ...ingService.ts |   91.41 |    85.15 |   95.65 |   91.41 | ...2117,2144-2145 
  ...ttribution.ts |   91.73 |    87.71 |      90 |   91.73 | ...80-685,826-827 
  ...utSlimming.ts |    97.2 |    93.93 |     100 |    97.2 | ...39-340,378-381 
  cronScheduler.ts |   94.17 |    90.45 |      98 |   94.17 | ...1333,1736-1737 
  cronTasksFile.ts |   95.49 |    90.82 |     100 |   95.49 | ...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 |     100 |      100 |     100 |     100 |                   
  ...temService.ts |    92.8 |    84.68 |   94.11 |    92.8 | ...41,467-474,519 
  ...ratedFiles.ts |      96 |    88.23 |     100 |      96 | 119-120,146-147   
  gitInit.ts       |     100 |      100 |     100 |     100 |                   
  ...reeService.ts |    73.7 |    68.49 |   95.83 |    73.7 | ...2196,2225-2226 
  ...on-service.ts |   87.38 |       72 |     100 |   87.38 | ...01-305,343-344 
  ...references.ts |   98.39 |    88.76 |     100 |   98.39 | 154-155,215-216   
  ...ionService.ts |   98.22 |    97.34 |     100 |   98.22 | ...75-676,723-724 
  ...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.3 |    91.22 |     100 |    97.3 | ...53-454,611-612 
  ...ttachments.ts |   97.74 |    90.85 |     100 |   97.74 | 298-308,646       
  ...ersistence.ts |   90.95 |    78.75 |     100 |   90.95 | ...78,963-964,992 
  ...on-service.ts |   94.49 |    92.26 |   97.14 |   94.49 | ...98-600,656-664 
  ...ce-service.ts |   98.38 |    93.75 |   88.88 |   98.38 | 63-64             
  ...ipt-reader.ts |   93.69 |    89.22 |   96.07 |   93.69 | ...1094-1095,1158 
  ...est-helper.ts |       0 |        0 |       0 |       0 | 1-65              
  ...iter-lease.ts |   82.81 |    74.08 |    97.5 |   82.81 | ...2364,2376-2379 
  sessionRecap.ts  |   67.56 |    43.47 |     100 |   67.56 | ...60,178,180-183 
  ...ionService.ts |   88.94 |    83.92 |   97.14 |   88.94 | ...2450,2520-2540 
  sessionTitle.ts  |   94.19 |    73.21 |     100 |   94.19 | ...43-246,277-278 
  ...ionService.ts |   84.35 |    78.37 |   97.14 |   84.35 | ...2472,2478-2483 
  ...pInhibitor.ts |   97.42 |    92.77 |     100 |   97.42 | ...30,169,369-370 
  ...Estimation.ts |     100 |    88.23 |     100 |     100 | 118-119           
  ...ageService.ts |   97.76 |    91.59 |   93.75 |   97.76 | ...61-262,366,567 
  ...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.72 |    84.07 |     100 |   90.72 | ...06-509,561-562 
  ...reeCleanup.ts |   14.42 |      100 |   33.33 |   14.42 | 58-186            
  ...ionService.ts |   87.98 |    86.95 |     100 |   87.98 | ...38-439,455-456 
 ...icrocompaction |    98.9 |    95.06 |     100 |    98.9 |                   
  microcompact.ts  |    98.9 |    95.06 |     100 |    98.9 | ...40,749,758-759 
 ...s/visionBridge |   98.81 |    92.12 |     100 |   98.81 |                   
  ...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             
  ...-constants.ts |     100 |      100 |     100 |     100 |                   
  ...ge-service.ts |   98.61 |     94.7 |     100 |   98.61 | ...06,666,679-680 
 src/skills        |   89.29 |    85.87 |   93.61 |   89.29 |                   
  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.5 |     100 |   94.84 | ...03,223,235-237 
  skill-manager.ts |   84.82 |    85.29 |   83.33 |   84.82 | ...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 |     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     |   87.72 |    89.01 |   96.55 |   87.72 |                   
  ...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 |   84.48 |    85.91 |   94.87 |   84.48 | ...1582,1659-1660 
  types.ts         |     100 |      100 |     100 |     100 |                   
  validation.ts    |   92.46 |    95.18 |     100 |   92.46 | 47-52,63-68,71-76 
 src/telemetry     |   81.39 |    83.87 |   84.66 |   81.39 |                   
  ...ty-tracker.ts |     100 |      100 |     100 |     100 |                   
  config.ts        |     100 |      100 |     100 |     100 |                   
  constants.ts     |     100 |      100 |     100 |     100 |                   
  ...on-metrics.ts |   99.07 |    80.95 |     100 |   99.07 | 183,197           
  ...on-tracing.ts |   76.31 |    74.62 |   73.68 |   76.31 | ...80,387-389,405 
  ...attributes.ts |   95.15 |    87.27 |     100 |   95.15 | ...97-198,216-217 
  ...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.78 |    83.33 |   55.55 |   65.78 | ...04-105,108-109 
  ...ai-content.ts |    74.5 |    66.41 |   91.66 |    74.5 | ...1480,1493-1502 
  ...i-provider.ts |     100 |       99 |     100 |     100 | 99                
  ...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.1 |    95.72 |      95 |    99.1 | 145,369-370       
  ...t.circular.ts |       0 |        0 |       0 |       0 | 1-128             
  loggers.ts       |   57.84 |    74.16 |   65.45 |   57.84 | ...1438,1455-1475 
  metrics.ts       |   80.04 |    82.75 |   80.32 |   80.04 | ...1105,1108-1119 
  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      |   91.06 |    87.15 |   68.75 |   91.06 | ...32,478-479,495 
  sdk.ts           |   79.22 |    89.18 |   63.63 |   79.22 | ...57-161,199-221 
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  ...on-tracing.ts |    91.1 |    88.68 |   96.77 |    91.1 | ...1737,1768-1771 
  ...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         |   82.51 |    94.77 |   86.04 |   82.51 | ...1374,1378-1385 
  uiTelemetry.ts   |   97.18 |    93.93 |      88 |   97.18 | ...70,314,461-462 
 ...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.02 |    98.41 |   82.92 |   96.02 |                   
  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 |   78.78 |   94.85 | ...53,227-228,241 
  ...aceContext.ts |     100 |      100 |     100 |     100 |                   
 src/tools         |    86.1 |    84.89 |   88.42 |    86.1 |                   
  ...erQuestion.ts |   89.71 |    80.76 |   91.66 |   89.71 | ...66-367,374-375 
  ...-registrar.ts |    77.7 |    66.66 |   66.66 |    77.7 | ...72-277,292-294 
  ...ub-session.ts |   89.67 |     91.3 |   81.81 |   89.67 | ...03-304,315-322 
  cron-create.ts   |   90.64 |    92.85 |   72.72 |   90.64 | ...,73-74,223-231 
  cron-delete.ts   |   97.56 |      100 |   83.33 |   97.56 | 31-32             
  cron-list.ts     |   98.23 |    95.34 |    87.5 |   98.23 | 57-58             
  diffOptions.ts   |     100 |      100 |     100 |     100 |                   
  display-image.ts |   87.42 |    84.84 |   88.88 |   87.42 | ...29-134,194-195 
  edit.ts          |    82.7 |    86.77 |   81.25 |    82.7 | ...43-744,863-913 
  ...r-worktree.ts |   83.14 |    67.56 |    87.5 |   83.14 | ...84-187,278-279 
  enterPlanMode.ts |      85 |     82.6 |    87.5 |      85 | ...28-133,161-175 
  exit-worktree.ts |   83.29 |    83.65 |   94.44 |   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.61 |   85.71 |   90.73 | ...76-677,727-728 
  ...adTracking.ts |     100 |      100 |     100 |     100 |                   
  image-gen.ts     |   91.66 |    77.41 |    90.9 |   91.66 | ...13-214,221-222 
  list-agents.ts   |   94.02 |    82.35 |   83.33 |   94.02 | 31-32,47-48       
  loop-wakeup.ts   |   99.27 |    92.85 |     100 |   99.27 | 45                
  ls.ts            |   96.74 |    90.27 |     100 |   96.74 | 176-181,212,216   
  lsp.ts           |   72.71 |     59.5 |   90.32 |   72.71 | ...1212,1214-1215 
  ...nt-manager.ts |   81.49 |     80.1 |   85.71 |   81.49 | ...3217,3219-3220 
  mcp-client.ts    |   79.87 |    85.58 |   89.47 |   79.87 | ...2259,2263-2266 
  ...ry-timeout.ts |     100 |      100 |     100 |     100 |                   
  mcp-errors.ts    |     100 |      100 |     100 |     100 |                   
  ...pool-entry.ts |   77.56 |    84.11 |   77.14 |   77.56 | ...1291,1299-1300 
  ...ool-events.ts |       8 |        0 |       0 |       8 | 132-158           
  mcp-pool-key.ts  |   97.46 |    93.93 |     100 |   97.46 | 175-176           
  ...ce-content.ts |   96.55 |    91.17 |     100 |   96.55 | 80-82             
  mcp-retry.ts     |   97.67 |    95.65 |     100 |   97.67 | 131-132           
  mcp-status.ts    |     100 |      100 |     100 |     100 |                   
  mcp-tool.ts      |   98.34 |    93.65 |     100 |   98.34 | ...-982,1037-1038 
  ...sport-pool.ts |   83.49 |    80.15 |   84.61 |   83.49 | ...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.74 |    84.28 |   88.46 |   91.74 | ...93,606,804-809 
  notebook-edit.ts |   85.55 |    77.39 |   81.25 |   85.55 | ...86-902,948-949 
  ...escendants.ts |   36.17 |    64.51 |   55.55 |   36.17 | ...46-310,385-390 
  ...nforcement.ts |   81.45 |    89.74 |     100 |   81.45 | 147-158,207-220   
  read-file.ts     |   95.49 |    88.52 |   86.66 |   95.49 | ...49,464,536-537 
  ...p-resource.ts |   96.85 |      100 |   91.66 |   96.85 | 92-96             
  ...d-artifact.ts |   91.18 |    86.71 |    87.5 |   91.18 | ...26-427,441-453 
  ripGrep.ts       |    94.6 |    87.26 |   95.23 |    94.6 | ...33-734,740-741 
  ...-transport.ts |   71.42 |    55.55 |   71.42 |   71.42 | ...36-137,143-144 
  send-message.ts  |   81.13 |    89.74 |    62.5 |   81.13 | ...80-286,363-371 
  ...n-mcp-view.ts |   93.57 |     92.3 |      90 |   93.57 | 122-130           
  shell.ts         |   78.67 |    84.04 |   91.91 |   78.67 | ...5019,5082-5083 
  skill-utils.ts   |     100 |      100 |     100 |     100 |                   
  skill.ts         |   91.39 |    92.55 |      90 |   91.39 | ...84,488,534-556 
  ...eticOutput.ts |   95.12 |      100 |      80 |   95.12 | 87-88             
  task-create.ts   |    94.4 |    93.33 |   81.81 |    94.4 | 45-49,63-64,95    
  task-list.ts     |   73.38 |    77.77 |   83.33 |   73.38 | ...02,105,109-116 
  task-stop.ts     |   93.14 |    96.15 |   85.71 |   93.14 | 39-40,54-64       
  task-update.ts   |   82.89 |    83.92 |    92.3 |   82.89 | ...14-422,454-465 
  team-create.ts   |   97.22 |    85.71 |   83.33 |   97.22 | 48-49,129-130     
  team-delete.ts   |   86.74 |    83.33 |   83.33 |   86.74 | 37-38,42-48,72-73 
  ...n-approval.ts |   92.14 |    96.77 |   77.77 |   92.14 | 38-39,42-43,93-99 
  todoWrite.ts     |   95.13 |    87.85 |   93.33 |   95.13 | ...23-527,540-545 
  tool-error.ts    |     100 |      100 |     100 |     100 |                   
  tool-names.ts    |     100 |      100 |     100 |     100 |                   
  tool-registry.ts |   78.57 |    79.59 |    82.6 |   78.57 | ...89-990,998-999 
  tool-search.ts   |   96.19 |    89.72 |   93.33 |   96.19 | ...09,259-264,426 
  tools.ts         |   93.11 |    92.53 |   91.66 |   93.11 | ...69-570,586-592 
  ...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.53 |    83.57 |      80 |   90.53 | ...1007,1065-1068 
  write-file.ts    |    86.7 |    84.92 |   88.88 |    86.7 | ...24-827,864-899 
  zoom-image.ts    |   95.76 |    93.75 |      90 |   95.76 | 54-59,203-204     
 src/tools/agent   |   87.22 |    87.68 |   88.69 |   87.22 |                   
  agent.ts         |   85.84 |    86.59 |   86.31 |   85.84 | ...4315,4337-4347 
  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 |                   
 ...s/computer-use |   90.21 |    82.17 |   78.08 |   90.21 |                   
  bootstrap.ts     |   59.42 |    80.95 |   41.66 |   59.42 | ...35-339,341-345 
  client.ts        |   80.11 |       90 |   77.77 |   80.11 | ...97,242-243,274 
  constants.ts     |     100 |    94.73 |     100 |     100 | 129,256           
  downloader.ts    |   65.29 |    52.77 |   58.33 |   65.29 | ...99-300,316-355 
  index.ts         |     100 |      100 |     100 |     100 |                   
  install-state.ts |   94.44 |    72.72 |     100 |   94.44 | 44-45             
  ...n-detector.ts |     100 |     87.5 |     100 |     100 | 50                
  schemas.ts       |     100 |      100 |     100 |     100 |                   
  tool.ts          |    96.3 |    85.71 |     100 |    96.3 | 75-76,184,252-258 
 ...tools/workflow |   86.24 |    84.81 |      75 |   86.24 |                   
  workflow.ts      |   86.24 |    84.81 |      75 |   86.24 | ...61,506,508-509 
 src/utils         |   92.84 |    89.61 |   96.85 |   92.84 |                   
  LruCache.ts      |     100 |      100 |     100 |     100 |                   
  ...Controller.ts |     100 |      100 |     100 |     100 |                   
  ...ssageQueue.ts |     100 |      100 |     100 |     100 |                   
  ...cFileWrite.ts |   94.94 |    92.47 |     100 |   94.94 | ...43-544,651-655 
  bareMode.ts      |   81.81 |      100 |      50 |   81.81 | 18-19             
  ...ry-content.ts |   98.45 |    95.45 |     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 |                   
  ...ncyLimiter.ts |   94.64 |    95.23 |     100 |   94.64 | 64-66             
  ...igResolver.ts |     100 |      100 |     100 |     100 |                   
  ...engthError.ts |   91.06 |    89.47 |     100 |   91.06 | ...46-147,154-155 
  ...n-branches.ts |   95.81 |    93.95 |      95 |   95.81 | ...91-492,504-517 
  ...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   |   96.66 |    96.66 |   88.88 |   96.66 | 192-196           
  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     
  ...entContext.ts |   96.63 |    90.13 |   96.66 |   96.63 | ...42,444-445,512 
  errorParsing.ts  |     100 |      100 |     100 |     100 |                   
  ...rReporting.ts |   95.65 |    93.33 |     100 |   95.65 | 37-38             
  errors.ts        |   82.62 |    94.32 |    61.9 |   82.62 | ...62-378,382-388 
  fetch.ts         |   90.68 |    82.51 |     100 |   90.68 | ...72,483-484,503 
  fileUtils.ts     |   94.87 |    92.95 |   96.15 |   94.87 | ...1907,1915-1916 
  forkedAgent.ts   |   92.45 |    82.35 |   93.75 |   92.45 | ...34,642,647-654 
  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 
  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.02 |    81.25 |   85.71 |   78.02 | ...22-123,147-198 
  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.12 |    93.33 |     100 |   95.12 | ...68-172,240-244 
  ...ionManager.ts |     100 |     90.9 |     100 |     100 | 27                
  ...lPromptIds.ts |     100 |      100 |     100 |     100 |                   
  ...on-context.ts |     100 |      100 |     100 |     100 |                   
  jsonl-utils.ts   |   95.27 |     93.1 |     100 |   95.27 | ...16-317,359-362 
  ...-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                
  ...iagnostics.ts |    96.4 |     94.2 |     100 |    96.4 | ...66,293-294,376 
  ...yDiscovery.ts |    92.4 |    89.13 |     100 |    92.4 | ...28,331,522-525 
  ...tProcessor.ts |   94.01 |       90 |     100 |   94.01 | ...47-353,445-446 
  ...Inspectors.ts |     100 |      100 |     100 |     100 |                   
  modelId.ts       |   98.96 |    98.21 |     100 |   98.96 | 153               
  ...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         |   93.99 |    92.85 |     100 |   93.99 | ...88-489,491-493 
  pdf.ts           |   92.17 |    85.81 |     100 |   92.17 | ...64-565,606-611 
  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.15 |     100 |   96.98 | ...87-688,763-764 
  readManyFiles.ts |   95.75 |    80.86 |     100 |   95.75 | ...05,558,568-572 
  retry.ts         |   96.09 |    92.52 |     100 |   96.09 | ...67,558-559,577 
  retryContext.ts  |     100 |      100 |     100 |     100 |                   
  ...sification.ts |   97.63 |    97.02 |     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 
  ...sDiscovery.ts |   97.46 |    93.05 |     100 |   97.46 | ...04,182-183,202 
  ...iagnostics.ts |   83.08 |     67.5 |   92.59 |   83.08 | ...23,543-544,550 
  ...tchOptions.ts |   84.87 |    86.61 |   96.29 |   84.87 | ...71,696,725-734 
  ...odelPrefix.ts |     100 |      100 |     100 |     100 |                   
  runtimeStatus.ts |    97.5 |    89.74 |     100 |    97.5 | 162-163           
  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.03 |    97.75 |     100 |   98.03 | 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 |      100 |     100 |     100 |                   
  ...orageUtils.ts |   95.98 |    83.96 |     100 |   95.98 | ...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.07 |    88.34 |     100 |   86.07 | ...2269,2276-2280 
  ...lAstParser.ts |   98.16 |    91.91 |     100 |   98.16 | ...1244-1246,1256 
  ...ContextEnv.ts |     100 |       92 |     100 |     100 | 50-52             
  ...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 |       50 |     100 |   77.77 | 44,54-59          
  ...e-encoding.ts |   85.96 |    76.47 |     100 |   85.96 | 58-61,64-65,78-79 
  ...emEncoding.ts |   96.36 |    91.17 |     100 |   96.36 | 59-60,124-125     
  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             
  ...name-utils.ts |     100 |      100 |     100 |     100 |                   
  ...-finalizer.ts |   97.66 |     90.9 |     100 |   97.66 | 165-166,168-172   
  tool-utils.ts    |    95.2 |    93.61 |     100 |    95.2 | ...58-159,162-163 
  ...ultCleanup.ts |   54.62 |    57.14 |      75 |   54.62 | ...03-105,108-134 
  ...Compaction.ts |   96.13 |    96.42 |     100 |   96.13 | ...34-339,341-346 
  ...pt-records.ts |   85.89 |    83.63 |     100 |   85.89 | ...91-395,425-440 
  truncation.ts    |   90.56 |    90.43 |     100 |   90.56 | ...35-443,480-486 
  windowsPath.ts   |   89.47 |    79.31 |     100 |   89.47 | ...57-58,62,90-91 
  ...aceContext.ts |   95.81 |    89.39 |     100 |   95.81 | ...74-275,299-301 
  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.72 |   94.73 |   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.43 |   89.47 |   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 |   69.76 |    75.47 |   85.29 |   69.76 |                   
  ...eTokenizer.ts |   65.72 |    74.02 |    92.3 |   65.72 | ...65-466,479-533 
  index.ts         |     100 |      100 |     100 |     100 |                   
  ...tTokenizer.ts |   68.39 |    69.49 |    90.9 |   68.39 | ...24-325,327-328 
  ...ageFormats.ts |   76.92 |      100 |   33.33 |   76.92 | 46-49,56-57       
  textTokenizer.ts |     100 |      100 |     100 |     100 |                   
  types.ts         |       0 |        0 |       0 |       0 | 1                 
-------------------|---------|----------|---------|---------|-------------------

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

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Unresolved, please confirm: [Critical] Existing CHANGES_REQUESTED review by @qwen-code-ci-bot (triage stage 1a, review 4840811017): the PR body does not follow the PR template (missing required headings incl. Reviewer Test Plan and Linked Issues). This is a PR-body/process requirement of the project's triage gate — it cannot be ruled on from the code at the reviewed commit. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

Comment on lines +620 to +622
runtimeMemory &&
runtimeMemory.pressure.mode === 'observe' &&
runtimeMemory.pressure.level !== 'normal'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The level !== 'normal' clause is the only part of this gate with no test on either side — the sole issue-raising test runs only at critical (the 1 MiB denominator), and no budget-resolving test asserts the issue is absent at normal or present at soft/hard. — Failure scenario: mutation-probed live: (a) deleting this clause keeps the entire suite green (including the booted-daemon e2e) while default observe mode raises daemon_memory_pressure on a healthy daemon, flipping top-level status from ok to warning on every /daemon/status response — exactly the false-positive status flip --memory-pressure-mode off exists to opt out of; (b) tightening to level === 'critical' is equally green and silently drops the warning at soft/hard. Both contradict the documented contract ("raises a daemon_memory_pressure issue whenever the level leaves normal"). Suggested fix: add (1) a budget-resolving default-mode test asserting no daemon_memory_pressure issue and status === 'ok' at a realistic denominator, and (2) a denominator that lands the test process in soft asserting one warning issue appears.

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

// Ties go to RSS. Arbitrary but deterministic, and it only arises when the
// two ratios are equal — in which case either name describes the same
// number, and `level` is unaffected either way.
else source = rssRatio >= heapRatio ? 'rss' : 'heap';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The documented tie-break ("ties are reported as rss", added to qwen-serve-protocol.md by this same diff) has zero test coverage — every exact-tie input in the unit suite omits source from its assertions. — Failure scenario: mutation-probed: changing >= to > here flips source from rss to heap whenever both denominators are measurable and the ratios are exactly equal, and no test fails. The suite itself constructs exact ties (2 GB/8 GB vs 1 GB/4 GB — both exactly 0.25) but asserts only the six raw figures. Consumers — including this feature's own issue-message ternary source === 'heap' ? … — then silently get the other denominator named, contradicting the wire contract. Suggested fix: add source: 'rss' to the toMatchObject of the existing exact-tie case in daemon-memory-pressure.test.ts (or add a dedicated one-line tie case).

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

Comment on lines +19 to +21
export const SOFT_PRESSURE_RATIO = 0.5;
export const HARD_PRESSURE_RATIO = 0.65;
export const CRITICAL_PRESSURE_RATIO = 0.8;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] These constants duplicate core's DEFAULT_PRESSURE_CONFIG (0.5/0.65/0.8 in memoryPressureMonitor.ts) with nothing pinning the two copies together. The duplication rationale in the comment checks out (the monitor is private to Config.initialize(); extracting from packages/core/src/services/** is maintainer-gated) — the gap is the unbridged interim drift. — Failure scenario: DEFAULT_PRESSURE_CONFIG is a live tuning knob (spread in Config, validated by validateMemoryPressureConfig); if it is retuned, the daemon copy stays stale, so soft/hard/critical silently mean different things in the interactive CLI vs daemon status, and the "Thresholds mirror MemoryPressureMonitor … the established contract" claim above becomes false with no test red. Suggested fix: pin the copy — a test asserting the three constants equal the matching DEFAULT_PRESSURE_CONFIG fields (a test-time import only, so the runtime module-graph concern does not apply), and/or file the follow-up issue the comment defers the extraction to.

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

Comment thread packages/cli/src/serve/daemon-status.ts Outdated
Comment on lines +640 to +641
`Daemon memory pressure is ${level} at ` +
`${(ratio * 100).toFixed(0)}% of ` +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] toFixed(0) can display the next level's documented threshold while naming the current level. — Failure scenario: executed arithmetic — a ratio of 0.795–0.799 classifies as hard but the message reads "Daemon memory pressure is hard at 80% of available memory.", and 80% is precisely critical's documented threshold (0.8); 0.645–0.649 reads "soft at 65%", hard's threshold. An oncall engineer comparing the message against the documented thresholds sees a contradiction and wastes triage time doubting the classifier — in the one feature whose purpose is trustworthy triage. The band is narrow (within 0.5 percentage points below a threshold), but a daemon climbing toward a threshold passes through it continuously.

Suggested change
`Daemon memory pressure is ${level} at ` +
`${(ratio * 100).toFixed(0)}% of ` +
`Daemon memory pressure is ${level} at ` +
`${(ratio * 100).toFixed(1)}% of ` +

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

Comment on lines +95 to +96
| 'daemon_log_degraded'
| 'daemon_memory_pressure';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Test-efficacy probe: reverting this hunk on its own leaves every test in this diff green — no runtime test turns on this declaration; the only guard is TypeScript compilation, which CI runs via npm run build in the e2e workflow (ci.yml has no dedicated typecheck step). — Failure scenario: vitest runs without type-checking, so an edit that removes or renames this issue code while keeping runtime behavior compiling passes the whole suite green; only the compiler notices, and only on a CI path that builds. The cost is silent drift of the status wire contract. (The feature as a whole IS gated — reverting the entire source fails 5 assertions — this says only that this hunk is not what any test turns on.) Suggested fix: pin the issue emission with a runtime assertion — the existing test filters issues by code but reads only severity; extend it so the code string is test-gated, not tsc-gated only.

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

* it. Hoisting it out would restructure the block for a path that does not
* need the reading.
*/
pressure: DaemonMemoryPressure & { mode: 'off' | 'observe' };

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] Test-efficacy probe: reverting this hunk (the pressure field declaration) on its own leaves every test in this diff green — the runtime wiring still populates the object, and the existing toEqual blocks assert only pressure: objectContaining({ mode }), which survives a declaration-only revert. As with the sibling hunk on the issue-code union, the only guard is TypeScript compilation (run in CI via the e2e workflow's npm run build, not a dedicated ci.yml typecheck step). — Failure scenario: a rename or removal here that keeps the runtime shape compiling passes the entire test suite green; shape drift of the runtime.memory.pressure wire payload is caught only by the compiler. Suggested fix: pin the wire shape with runtime assertions in daemon-status.test.ts (the full key-set of pressurelevel/ratio/source/mode plus the six raw figures), so the declaration is test-gated, not tsc-gated only.

— qwen3.8-max-preview via Qwen Code /review (v0.21.4)

@doudouOUC doudouOUC changed the title feat(serve): observe daemon memory pressure against a real denominator feat(serve): observe daemon and child memory against real denominators Aug 3, 2026

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

[Critical] Still-standing blocker from round 1 (re-checked this round against the live PR description): the PR body does not follow the PR template — it uses freeform sections (## Why / ## What / ## The flag / ## Scope / ## Compatibility / ## Verification) and is missing every required heading from .github/pull_request_template.md (What this PR does, Why it's needed, Reviewer Test Plan with How to verify / Evidence / Tested on, Risk & Scope, Linked Issues, and the Chinese translation block). This is the PR-body/process requirement behind the existing CHANGES_REQUESTED triage review (stage 1a, review 4840811017); the body has not changed since, so the blocker still stands. The code itself is reviewed in the inline findings.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +165 to +166
throughout — read `runtime.memory.children` beside it, which sums the live
children's own RSS (and says via `sampled` how many actually reported).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The recipe recommends runtime.memory.children without disclosing that child-RSS sampling only runs while an SSE/WS watcher is attached (the sampler gate in run-qwen-serve.ts), and that each reading is dropped ~30s after the last watcher leaves (STALE_CHILD_RESOURCE_MS). The sibling protocol doc discloses both; this recipe cross-references neither. — Failure scenario: an operator follows the recipe's own usage pattern — a one-shot curl against /daemon/status with no streaming client connected — and within ~30s of having no watcher the response reports { rssBytes: 0, sampled: 0 } even with live, growing children: a false negative for exactly the failure mode this recipe exists to catch, with no stated reason or remedy.

Suggested change
throughout — read `runtime.memory.children` beside it, which sums the live
children's own RSS (and says via `sampled` how many actually reported).
throughout — read `runtime.memory.children` beside it, which sums the live
children's own RSS (and says via `sampled` how many actually reported). Sampling only runs while an SSE/WS client is attached, and readings age out ~30s after the last watcher leaves, so `sampled: 0` means nothing was measured — connect a watcher (e.g. a session SSE stream) and re-poll before trusting a zero sum.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +4463 to +4464
for (const managed of workspaceRegistry.listManaged()) {
void managed.bridge.refreshChildResource?.().catch((err) => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This sampler fan-out (refresh every managed workspace instead of primary-only) has zero test coverage. Probe-verified this round: reverting to the old primary-only call keeps the whole suite green, while a dead-comparator control proves the harness can discriminate in the one candidate test. run-qwen-serve.test.ts opens no SSE/WS watcher (its own comment says the watch gate never fires) and never references refreshChildResource. — Failure scenario: a future refactor restoring primary-only refresh passes CI silently; on a multi-workspace daemon with a watcher, non-primary children would never be polled, their cached readings would age out of the ~30s window, and children.sampled/rssBytes would shrink to the primary child alone — the precise under-count this change was made to fix.

Suggested fix: drive one sampler tick (fake timers or an injectable interval) with the watcher gate satisfied and two or more managed runtimes whose bridges expose refreshChildResource spies, and assert every managed bridge — not only the primary — is refreshed.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +19533 to +19534
expect(fresh!.ageMs).toBeGreaterThanOrEqual(0);
expect(fresh!.ageMs).toBeLessThan(30_000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The fresh-reading assertions bound ageMs only to [0, 30_000) at reading-time ≈ 0, which holds for any positive window and any in-range computation. Probe-verified this round: both an ageMs: 0 mutant and an inverted-age mutant (STALE_CHILD_RESOURCE_MS - ageMs) in getChildResourceSnapshot keep this suite green, because the staleness-cliff half computes its own comparison and every downstream test injects ages as literals. — Failure scenario: oldestReadingAgeMs then reports a sum whose parts were taken up to 30s apart as instantaneous (constant 0) or reports the newest reading's age as the oldest — exactly the misreading the field's own comment says it exists to prevent.

Suggested fix: advance the clock via the test's existing Date.now seam to a still-fresh instant (e.g. realNow() + 5_000) and assert a bounded range around it (>= 5_000, < 10_000) — pinning direction, magnitude, and reference timestamp at once.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +67 to +69
* This covers the daemon root process only. Aggregate child RSS is a separate
* measurement — the sampler currently reads the primary ACP child alone — so
* this figure must not be read as process-tree pressure.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This JSDoc says 'the sampler currently reads the primary ACP child alone' of aggregate child RSS — but this same PR makes runtime.memory.children span every live child (the listManaged() refresh fan-out in run-qwen-serve.ts); only the metrics ring's childRssBytes gauge stays primary-only. The design doc and run-qwen-serve.ts both split the two measurements explicitly, so the clause is false of the aggregate within the PR itself (flagged independently by two review passes). — Failure scenario: a future reader extending pressure to the process tree, or triaging daemon memory from this module's doc, is told the sibling measurement is primary-only and under-attributes child growth, contradicting qwen-serve-protocol.md. The conclusion the sentence supports (pressure is root-only) still holds; the stated reason is wrong.

Suggested change
* This covers the daemon root process only. Aggregate child RSS is a separate
* measurement the sampler currently reads the primary ACP child alone so
* this figure must not be read as process-tree pressure.
* This covers the daemon root process only. Aggregate child RSS is a separate
* measurement (`runtime.memory.children`, which sums every live child's cached
* reading); the metrics ring's `childRssBytes` gauge stays primary-only. Either
* way this figure must not be read as process-tree pressure.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +357 to +359
list: () => runtimes,
listManaged: () => runtimes,
listEntries: () => runtimes.map(() => ({})),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] No test pins that the children sum enumerates listManaged() rather than list() — every test that asserts children stubs the two enumerators identically. Probe-verified this round: swapping the sum loop in daemon-status.ts from managedRuntimes to the list() result keeps all 44 tests green. — Failure scenario: the two calls sit a few lines apart in buildDaemonStatusResponse (list() is active-state only, listManaged() includes draining/process-holding runtimes) — an easy swap in a future refactor. A draining workspace's live child would then silently drop out of children.rssBytes/sampled while activeAcpChildren still counts it, under-reporting child RSS in exactly the drain window and violating the sum's documented same-pass promise.

Suggested fix: in the existing 'counts a draining workspace that still holds a live child' test, give the draining bridge a getChildResourceSnapshot returning a reading and assert children includes it — pinning that the sum enumerates the process-holding set, not the active-state set.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment thread packages/cli/src/serve/daemon-status.ts Outdated
Comment on lines +394 to +396
* only reaches direct-embed callers: `runQwenServe` resolves the budget
* before the bootstrap app exists, so every daemon an operator runs reports
* it. Hoisting it out would restructure the block for a path that does not

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This JSDoc ties pressure's absence to 'no budget resolved' and claims 'every daemon an operator runs reports it' — but the bootstrap /daemon/status handler populates limits.memory from the resolved budget yet omits runtime.memory wholesale, and /daemon/status is a bootstrap route. Traced this round: runQwenServeImpl resolves opts.daemonMemoryBudget before creating the bootstrap app, so during the startup window, and for a daemon whose runtime failed to start (the delegating app falls through to the bootstrap app for the daemon's lifetime), the response carries limits.memory with no runtime.memory.pressure despite a resolved budget. — Failure scenario: a client or alert written to this guarantee (budget resolved ⇒ pressure present) dereferences a missing field exactly in the failed-runtime state where the pressure reading would best explain the failure — the triage case this PR exists for.

Suggested change
* only reaches direct-embed callers: `runQwenServe` resolves the budget
* before the bootstrap app exists, so every daemon an operator runs reports
* it. Hoisting it out would restructure the block for a path that does not
* only reaches direct-embed callers: `runQwenServe` resolves the budget
* before the bootstrap app exists, so every daemon that reaches the full
* status route reports it. (The bootstrap `/daemon/status` response served
* during startup and after a runtime startup failure omits `runtime.memory`
* wholesale, regardless of the budget.) Hoisting it out would restructure the
* block for a path that does not

Alternatively, compute pressure in the bootstrap handler too — the root-process reading needs no runtime.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +753 to +755
pressure?: {
mode: 'off' | 'observe';
level: 'normal' | 'soft' | 'hard' | 'critical';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The new pressure/children mirrors are hand copies of the daemon's wire shape with nothing pinning the two together — no contract test, no fixture, and none of this file's conventional 'Manual mirror of … keep the two field lists in sync.' marker (see DaemonMetricsBucket above). The SDK can't import the source type (it depends on acp-bridge/core, not packages/cli), so the hand copy is structural; the shapes match field-for-field today (verified), so the hazard is prospective. The daemon side is drift-proofed by referencing the computed type — its own comment explains a hand copy would not be caught. — Failure scenario: a field added or renamed in DaemonMemoryPressure compiles cleanly everywhere; the daemon updates via the type reference while this mirror silently keeps the old field list, and SDK consumers type-check against a published shape the wire no longer sends (renamed field reads undefined, typed as present).

Suggested fix: add the file's conventional sync marker to both new blocks naming the source types, and/or an SDK-side wire-contract fixture parsed through DaemonStatusReport.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +544 to +547
expect(pressure.rssRatio).toBeCloseTo(
pressure.rssBytes / (4_096 * 1024 * 1024),
10,
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] No test pins the pressure numerator wiring (rssBytes: pressureMemory.rss, heapUsedBytes: pressureMemory.heapUsed at the single call site). Probe-verified this round: transposing those two adjacent values keeps daemon-status.test.ts + daemon-memory-pressure.test.ts + the e2e assertions all green — the arithmetic assertion below is circular (both sides read back the same echoed fields) and the e2e assertions are swap-invariant. — Failure scenario: whenever availableBytes ≠ heapLimitBytes — the common container case, since V8's heap_size_limit (~4.1 GB) exceeds small cgroup limits — the level flips: a 2 GB cgroup daemon with rss 1.9 GB / heapUsed 0.8 GB truly reads max(1.9/2, 0.8/4.1) = 0.95 → critical with an issue in observe mode, but swapped reads max(0.8/2, 1.9/4.1) = 0.46 → normal — a daemon one step from the OOM killer reports healthy. The inverse direction fabricates pressure that isn't there.

Suggested change
expect(pressure.rssRatio).toBeCloseTo(
pressure.rssBytes / (4_096 * 1024 * 1024),
10,
);
expect(pressure.rssRatio).toBeCloseTo(
pressure.rssBytes / (4_096 * 1024 * 1024),
10,
);
// V8 used heap is always a proper subset of the process resident set —
// pins the rss/heapUsed wiring at the call site (a transpose of the two
// adjacent values otherwise keeps this whole suite green).
expect(pressure.heapUsedBytes).toBeLessThan(pressure.rssBytes);

Probe-verified: the added assertion fails against the swap mutant and holds on the real wiring.

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +417 to +420
if (read.value !== 'off' && read.value !== 'observe') {
return { kind: 'fallback' };
}
options.memoryPressureMode = read.value;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The fast-path mapping for --memory-pressure-mode observe has zero assertion anywhere. Probe-verified this round: a constant-'off' mutant on the assignment keeps 119/119 tests green — the dedicated test asserts only the 'off' stored value and the 'enforce' fallback, and the parity-table entry for observe checks only parsed.kind, never the stored value. — Failure scenario: tryRunServeFastPath is the production parse path for plain qwen serve --memory-pressure-mode observe; under the mutant a deployment script explicitly pinning observe silently runs as off — figures still reported but daemon_memory_pressure never raised and the rollup never flipped, diverging from the yargs path, with no red test.

Suggested fix: in parses --memory-pressure-mode and falls back on an unknown value, also assert the stored value for observe in both spellings:

for (const argv of [
  ['serve', '--memory-pressure-mode', 'observe'],
  ['serve', '--memory-pressure-mode=observe'],
]) {
  expect(parseServeFastPathArgs(argv)).toMatchObject({
    kind: 'serve',
    options: { memoryPressureMode: 'observe' },
  });
}

— qwen3.8-max via Qwen Code /review (v0.21.3)

Comment on lines +354 to +355
* measured — either no watcher is gating the sampler open, or the daemon
* was built without a workspace registry to enumerate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This JSDoc enumerates exactly two causes for sampled: 0 with live children, phrased as complete — but omits the staleness age-out case, which is the case the staleness window exists for. Traced this round: childResourceAt is stamped only on a successful refreshChildResource poll (the bridge's own catch comment says a stuck child reads 0 rather than a frozen value), and isChannelLive() never checks reading freshness — so with a watcher attached and a registry present, a hung child yields activeAcpChildren ≥ 1 with sampled: 0. — Failure scenario: an operator triaging that state reads this JSDoc, concludes 'no watcher is gating the sampler open' (wrong — their dashboard holds one open) or 'no registry' (wrong), and chases a nonexistent configuration problem instead of seeing the actual signal: the child has been unresponsive for >30s. The omitted third cause is the only one of the three that indicates a real fault.

Suggested change
* measured either no watcher is gating the sampler open, or the daemon
* was built without a workspace registry to enumerate.
* measured either no watcher is gating the sampler open, the daemon
* was built without a workspace registry to enumerate, or every live
* child's last reading aged out of the staleness window (a child
* unresponsive to the resource poll for ~30s).

— qwen3.8-max via Qwen Code /review (v0.21.3)

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Worked through the review. Seven suggestions taken, one pushed back on — 361413ad.

Taken, and each mutant re-verified locally as failing now:

  • level !== 'normal' untested on either side — confirmed: deleting the clause kept the suite green while a healthy daemon raised daemon_memory_pressure on every response and flipped top-level status to warning. That is precisely the false positive --memory-pressure-mode off exists to opt out of, so it was the most consequential of the batch. Covered on both sides now: nothing raised at a realistic denominator, exactly one warning at a denominator sized to land the test process in soft.
  • list() vs listManaged() — the draining bridge now returns a reading, so the asserted byte count can only have come from that child. Summing over list() drops it and fails.
  • Denominator ternary uncovered — asserted in the both-modes test where source is deterministically rss. I did not plumb a memory-reading injection to force source: 'heap'; that widens buildDaemonStatusResponse's signature for one assertion, and the ternary's other branch is one string literal.
  • ageMs truthiness — added the ageMs: 0 case. Good catch: age is exactly 0 whenever a status read lands in the sampler's millisecond, and the truthiness mutant turns a measured-fresh sum into null, which that field's own docs say never means fresh. Also added the mixed-contract case.
  • Ascending age order — reordered descending. The reordering alone kills both last-wins and first-wins while Math.max stays green, which is a strictly better test for the same number of lines.
  • The two declaration-only hunks — pinned at runtime: the issue-code string, and the full key set of pressure. The observation that vitest does not typecheck is the real point here, and it applies beyond this diff.
  • toFixed(0) — fixed, and it is a genuine defect rather than a style nit: 0.795 renders as "hard at 80%" while 80% is critical's documented threshold. Took the suggested .toFixed(1).

Separately, the review's trace of the bootstrap route found a JSDoc claim of mine that was just wrong — I wrote that a resolved budget implies pressure is present, but the bootstrap /daemon/status omits runtime.memory wholesale, and that window is not only startup: a daemon whose runtime fails to start serves the bootstrap app for its lifetime, which is exactly when the reading would explain the most. Corrected.

Pushed back — the threshold constants duplicating core's DEFAULT_PRESSURE_CONFIG:

Agreed on the duplication, but importing is not available and the extraction is out of scope for this PR. MemoryPressureMonitor is constructed only by Config.initialize(), which the daemon never calls, and its limit resolution is a private method — so consolidating means extracting from packages/core/src/services/**, which AGENTS.md puts behind the maintainer-only gate. The constants carry a comment saying exactly this and naming the file, so the duplication is recorded rather than accidental. Happy to do the extraction as its own change if a maintainer wants it.

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Body is now on the template: ## What this PR does / ## Why it's needed / ## Reviewer Test Plan (with ### How to verify, ### Evidence (Before & After), ### Tested on) / ## Risk & Scope / ## Linked Issues, plus the Chinese <details>. The freeform sections map over intact — the two commits are now subsections of What this PR does, ## Scope and ## Compatibility fold into Risk & Scope, and ## Verification splits into reviewer-runnable steps under How to verify versus what I ran locally under Tested on.

The step worth a reviewer's time is #3: sampled <= activeAcpChildren must hold, and an idle daemon reports sampled: 0 because child-RSS sampling only runs while something is watching. Seeing a non-zero sum requires holding an SSE or WS stream open. That is the single thing most likely to look like a bug to someone who skips it, so it is called out inline.

On the red Test (ubuntu-latest, Node 22.x)

Not this PR. The 55 failures (10 + 45) reproduce identically across reruns, but they are repo-wide right now:

  • The 8 failing suites are all disk-writing (logger, Session, config, store, scheduled-tasks, virtual-subagent-sessions, dreamAgentPlanner, debugLogger) and none of them is in this PR's 30-file diff.
  • Unrelated PRs fail on the same suites — e.g. fix/hooks-security (run 30888849884) fails on logger.test.ts, Session.test.ts, config.test.ts, store.test.ts, scheduled-tasks.test.ts and virtual-subagent-sessions.test.ts, plus several more. A dozen-plus unrelated branches went red in the same window.
  • Root cause looks like shared-state collision on the self-hosted runners (/home/github-runner/actions-runner-25/…, so multiple runners per host sharing /tmp). Tests use fixed temp paths rather than mkdtemp — the clearest example is packages/core/src/core/logger.test.ts:39, TEST_HOME_DIR = path.join(os.tmpdir(), 'qwen-core-logger-home'). Two concurrent jobs use that same directory and one deletes it out from under the other, which is exactly the ENOENT … /tmp/qwen-core-logger-home/.qwen/tmp/… in the log.
  • These suites pass locally on this branch.

Rerunning does not help, since the collision recurs whenever jobs overlap. Happy to open a separate issue for the test-isolation fix — it is not in scope for this PR.

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Not reviewed: build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未审查:build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +352 to +353
.option('child-heap-mode', {
choices: ['off', 'observe'] as const,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-1: The PR description no longer discloses the diff's full scope: #8508 (per-child heap partition — child-heap-policy.ts, --child-heap-mode, committedProcessCount, reserve-before-decide, ~600 lines of this diff) was merged into this branch (mergeCommit 8e4b6f8) after the body was last updated, but the body still says 'Two commits' and Linked Issues names only #8462. — Failure scenario: issue #8051 lists #8508 as a separate in-flight item, and the chronology confirms it (body update 09:33Z, merge 12:32Z, fix commit e95dda0 at 14:18Z whose message says findings against #8508 'all still live on this branch now that it has merged'). A maintainer merging on the stated two-commit scope lands an unannounced third scope (a new wire field, a new flag, a new public package export). The body already uses the right pattern for #8462 — the same one-liner is owed to #8508.

中文说明

PR 描述未再披露 diff 的完整范围:#8508(每子进程堆分区——child-heap-policy.ts、--child-heap-mode、committedProcessCount、先预留后决策,约 600 行属于本 diff)在正文最后更新之后合入了本分支(mergeCommit 8e4b6f8),但正文仍写「两个 commit」,Linked Issues 也只提到 #8462。失效场景:issue #8051#8508 列为独立的进行中事项,时间线也证实了这一点(正文更新 09:33Z、合入 12:32Z、修复提交 e95dda0 于 14:18Z 且其提交信息称针对 #8508 的发现「在本分支合入后依然存在」)。维护者按所述的两 commit 范围合并时,会落地一个未公告的第三范围(新的 wire 字段、新的 flag、新的包公开导出)。正文对 #8462 已有正确的写法——对 #8508 也欠同样的一句说明。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment thread docs/users/qwen-serve.md Outdated
| `--max-connections <n>` | `256` | Listener-level TCP connection cap (`server.maxConnections`). Bounds raw socket count irrespective of session count — slow / phantom SSE clients get rejected at accept time once full. Raise alongside `--max-sessions` if your deployment expects many SSE subscribers per session. |
| `--memory-budget-mb <n>` | 50% of cgroup/host | Total memory budget in MB for the whole daemon process tree. When unset, derived as 50% of the cgroup limit or host memory; either way the effective value is capped at resolved available memory, and both the configured and effective figures are reported. Currently observation only — it does not change how any `qwen --acp` child is sized. Resolved figures appear under `limits.memory` in `GET /daemon/status`, alongside registered and live child counts and advisory per-child shares under `runtime.memory`. A host too small for the minimum reports `insufficientMemory` rather than being clamped upward; because the derived fraction is 50%, any host under ~2 GB trips this. Pass an explicit `--memory-budget-mb 1024` on such a host to override the derived figure (the flag still requires at least 1024 MB of available memory to clear the warning). Must be an integer in `[1024, 1048576]`. |
| `--memory-pressure-mode <mode>` | `observe` | Whether the daemon turns its own memory reading into a verdict. `observe` (default) reports the pressure level under `runtime.memory.pressure` in `GET /daemon/status` and raises a `daemon_memory_pressure` issue — a `warning`, so the overall `status` leaves `ok` — whenever the level leaves `normal`. `off` still reports every figure, including the level, but raises no issue, so the overall `status` is unchanged; use it while calibrating, or if you alert on the top-level status. The level is the worse of two ratios: RSS against available memory (what the cgroup OOM killer watches) and V8 heap used against this process's heap ceiling. It covers the daemon root process only; compare it against `runtime.memory.children.rssBytes` for the children. Nothing remediates in either mode. One of `off`, `observe`. |
| `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply — `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` — and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-2-1: Pattern: 'off models nothing' contradicts the code (this is occurrence 1 of 4). Off mode still computes and reports the full partition: the policy is built whenever a budget resolves regardless of mode (run-qwen-serve.ts:3504-3511), snapshot() has no mode gate, and toDaemonStatusMemoryLimits has none either — GET /daemon/status returns {mode:'off', maxConcurrentChildren, perChildCeilingMb, refusals:0}. What off withholds is only the refusal counting. — Failure scenario: an operator sets --child-heap-mode off expecting limits.memory.childHeap to disappear; it does not, and the reported model is misread as evidence the flag was ignored. Fixing only one surface leaves the others asserting a falsehood (also at 17-configuration.md:26, commands/serve.ts:361 --help text, and serve/types.ts childHeapMode JSDoc).

Suggested change
| `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. `observe` (default) reports what it would apply — `limits.memory.childHeap.perChildCeilingMb` and `maxConcurrentChildren` — and counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` models nothing. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |
| `--child-heap-mode <mode>` | `observe` | Whether the daemon models a per-child heap partition of `--memory-budget-mb`. Both modes report the modeled partition under `limits.memory.childHeap`; only `observe` counts spawns that would have exceeded the limit. **Nothing is applied**: no child is sized from the budget and no spawn is refused. `off` still reports the modeled figures but counts no refusals. A refusal count of 0 does **not** mean the partition would be safe to apply: children still run on the much larger host-derived ceiling, so a workload needing more old space than the modeled ceiling looks perfectly healthy here. Applying the partition ships with the measurement that can answer that. |
中文说明

模式问题:「off 什么都不建模」与代码矛盾(此为 4 处中的第 1 处)。off 模式仍会计算并上报完整分区:只要预算解析成功就会构建策略(run-qwen-serve.ts:3504-3511),snapshot() 无模式门控,toDaemonStatusMemoryLimits 也没有——GET /daemon/status 会返回 {mode:'off', maxConcurrentChildren, perChildCeilingMb, refusals:0}。off 唯一不做的是拒绝计数。失效场景:运维设置 --child-heap-mode off 后期望 limits.memory.childHeap 消失,实际并不会,上报的模型会被误读为 flag 未生效。只修一处会让其余各处继续断言假命题(另见 17-configuration.md:26、commands/serve.ts:361 的 --help 文本、serve/types.ts 的 childHeapMode JSDoc)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

| `--compacted-replay-max-bytes <n>` | positive integer | `4194304` | Byte cap for the bounded in-memory replay snapshot returned by `POST /session/:id/load`; hard cap is `268435456`. |
| `--memory-budget-mb <n>` | integer in `[1024, 1048576]` | 50% of cgroup-constrained or host memory, capped at the flag maximum (1048576 MB) | Total memory budget for the daemon process tree, capped at resolved available memory. Reported under `limits.memory`, and modeled into a per-child partition. Nothing applies it. Boot rejects out-of-range values. |
| `--memory-pressure-mode <mode>` | `off` \| `observe` | `observe` | Whether the daemon derives a memory-pressure level from its own RSS and V8 heap. Both modes report `runtime.memory.pressure`; only `observe` raises `daemon_memory_pressure`. Root process only; no remediation. |
| `--child-heap-mode <mode>` | `off \| observe` | `observe` | Whether the daemon models a per-child heap partition of the budget. `observe` reports it and counts spawns past it; nothing is applied. |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-2-2: Pattern: 'off models nothing' contradicts the code (occurrence 2 of 4 — see the qwen-serve.md comment for the trace). This row implies off neither models nor reports ('Whether the daemon models…; observe reports it'), but off mode still computes and reports limits.memory.childHeap; only refusal counting is disabled.

中文说明

模式问题:「off 什么都不建模」与代码矛盾(4 处中的第 2 处——推导过程见 qwen-serve.md 上的评论)。该行暗示 off 既不建模也不上报(「Whether the daemon models…; observe reports it」),但 off 模式仍会计算并上报 limits.memory.childHeap;只有拒绝计数被停用。

— qwen3.8-max via Qwen Code /review (v0.21.5)

'apply — `limits.memory.childHeap.perChildCeilingMb` and ' +
'`maxConcurrentChildren` — and counts spawns that would have ' +
'exceeded it. Nothing is applied: no child is sized from the ' +
'budget and no spawn is refused. `off` models nothing. Note a ' +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-2-3: Pattern: 'off models nothing' contradicts the code (occurrence 3 of 4, the --help text — see the qwen-serve.md comment for the trace). Restate as the sibling memory-pressure-mode help does: off still reports the modeled figures but counts no refusals.

中文说明

模式问题:「off 什么都不建模」与代码矛盾(4 处中的第 3 处,即 --help 文本——推导过程见 qwen-serve.md 上的评论)。请参照同级 memory-pressure-mode 的帮助文本改写:off 仍上报建模数值,只是不做拒绝计数。

— qwen3.8-max via Qwen Code /review (v0.21.5)

* needs a way to tell an operator in advance whether their workload fits
* the ceiling, and `refusals` cannot answer that: it counts admission
* pressure, while children still run on the far larger host-derived
* ceiling. `off` models nothing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-2-4: Pattern: 'off models nothing' contradicts the code (occurrence 4 of 4 — see the qwen-serve.md comment for the trace). The JSDoc's preceding sentences correctly describe observe-only counting; only this closing clause is false.

中文说明

模式问题:「off 什么都不建模」与代码矛盾(4 处中的第 4 处——推导过程见 qwen-serve.md 上的评论)。该 JSDoc 前几句对 observe 专属计数的描述是正确的,只有结尾这句为假。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +18 to +20
// Byte counts are exact multiples of the denominator so the ratio the function
// computes is exactly the one under test — rounding between the two would put
// a boundary case on the wrong side of its threshold without failing.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-20: Probe-verified: with HARD=0.65 and CRITICAL=0.8 against AVAILABLE = 8 GiB = 2^33, the boundary products are 5583457484.8 and 6871947673.6 — non-integers, so 4 of the 8 it.each rows are NOT 'exact multiples of the denominator'. The true invariant is that AVAILABLE is a power of two, making threshold scaling exact in IEEE 754 (probe: round-trips exact for 2^33, non-exact for a 12 GB denominator). — Failure scenario: a maintainer adding a threshold row or changing AVAILABLE to a realistic non-power-of-two value relies on the stated guard to judge whether boundary rows stay exact; the guard does not hold even for the shipped rows, so the edit silently drops the exactness the comment promises — weakening precisely the mutant-tight >= boundary check — and nothing tells the editor that power-of-two-ness is the property to preserve. Suggested fix: restate the true invariant (power-of-two AVAILABLE makes threshold scaling exact; a non-power-of-two AVAILABLE would let rounding land a boundary case off-threshold).

中文说明

经探针验证:HARD=0.65、CRITICAL=0.8 对 AVAILABLE = 8 GiB = 2^33 的边界乘积为 5583457484.8 与 6871947673.6——非整数,因此 8 个 it.each 行中有 4 行并非「分母的精确倍数」。真正的不变量是 AVAILABLE 为 2 的幂,使阈值缩放在 IEEE 754 下精确(探针:2^33 的往返精确,12 GB 分母则不精确)。失效场景:维护者新增阈值行或把 AVAILABLE 改成现实的非 2 的幂值时,会依赖注释所述的守卫来判断边界行是否仍精确;该守卫对已发布的行都不成立,编辑会悄悄丢掉注释承诺的精确性——恰恰削弱了变异敏感的 >= 边界检查——且没有任何东西提醒编辑者需要保持的是 2 的幂这一性质。建议:改写为真实不变量(2 的幂的 AVAILABLE 使阈值缩放精确;非 2 的幂的 AVAILABLE 会让舍入使边界用例偏离阈值)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +155 to +156
`level` is `normal` / `soft` / `hard` / `critical`, classified from `ratio` —
the worse of `rssRatio` (RSS against detected cgroup/host memory, which is what

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-21: The recipe defines level/source/unknown/modes but never states the mapping (soft >= 0.5, hard >= 0.65, critical >= 0.8), which exists only as constants in daemon-memory-pressure.ts; this same section says 'use off while calibrating thresholds against a real workload' without saying what they are, and no flag changes them. No operator-facing doc publishes the numbers, while this same file does publish other hardcoded thresholds (slow_client_warning 0.75/0.375) — so the omission is not house style. — Failure scenario: an oncall seeing level: 'soft' must judge severity without knowing soft fires at 50% of available memory — a healthy daemon can reach it, per the code's own 'not calibrated for a long-running daemon' comment; assuming soft ~= 75% overreacts, assuming critical ~= 95% under-reacts at 80%. Suggested fix: add one sentence — thresholds are ratio >= 0.5 (soft), >= 0.65 (hard), >= 0.8 (critical), inherited from the interactive CLI's memory-pressure monitor and not yet calibrated for a long-running daemon.

中文说明

配方定义了 level/source/unknown/模式,但从未给出映射(soft >= 0.5、hard >= 0.65、critical >= 0.8)——它们只存在于 daemon-memory-pressure.ts 的常量中;同一节还写着「use off while calibrating thresholds against a real workload」却不说明阈值是多少,也没有任何 flag 可以改它们。所有面向运维的文档都没有公布这些数值,而同一文件公布了其他硬编码阈值(slow_client_warning 0.75/0.375)——因此这个遗漏不是惯例。失效场景:oncall 看到 level: 'soft' 时必须在不知道 soft 在可用内存 50% 就触发的情况下判断严重度——按代码自己「未针对长运行 daemon 校准」的注释,健康 daemon 也可能达到;以为 soft ~= 75% 会过度反应,以为 critical ~= 95% 会在 80% 时反应不足。建议:补一句——阈值为 ratio >= 0.5(soft)、>= 0.65(hard)、>= 0.8(critical),继承自交互式 CLI 的内存压力监控器,尚未针对长运行 daemon 校准。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +679 to +680
memoryPressureMode: argv['memory-pressure-mode'],
childHeapMode: argv['child-heap-mode'],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-22: Probe-verified against the repo's real yargs parser: yargs collects duplicate occurrences of a non-array option into an array and validates each element against choices, so qwen serve --memory-pressure-mode off --memory-pressure-mode observe parses successfully as ['off','observe']. The handler forwards it unnormalized; daemon-status.ts:604 ?? 'observe' does not catch arrays, so pressure.mode serializes outside the declared union and the issue gate (mode === 'observe') is false for any array — no daemon_memory_pressure issue is ever raised, rollup stays ok at critical pressure; even observe observe (deploy template base flags + operator append) suppresses the alert. --child-heap-mode is symmetric. The fast path is last-wins on repeat; the yargs path produces the array — the two parse paths disagree. The pattern pre-exists for --mcp-budget-mode (the new options inherit it), and reaching the yargs path requires a fast-path fallback co-occurring with the duplicate. — Failure scenario: a deployment template that appends a flag the base already sets silently disables the pressure alert for the daemon's whole lifetime, with the wire reporting mode outside its declared union. Suggested fix: normalize or reject in the handler beside the existing validation (Array.isArray(mode) -> stderr + exit 1), or take the last element if last-wins is the intended semantic (matching the fast path).

中文说明

对仓库真实 yargs 解析器的探针验证:yargs 会把非数组选项的重复出现收集为数组,并对每个元素做 choices 校验,因此 qwen serve --memory-pressure-mode off --memory-pressure-mode observe 能成功解析为 ['off','observe']。handler 原样转发;daemon-status.ts:604 的 ?? 'observe' 接不住数组,于是 pressure.mode 序列化出声明的联合类型之外,且 issue 门控(mode === 'observe')对任何数组都为假——daemon_memory_pressure 永不产生,临界压力下 rollup 仍是 ok;即便 observe observe(部署模板基础 flags + 运维追加)也会压制告警。--child-heap-mode 对称。fast path 对重复取值是后者生效;yargs 路径产生数组——两条解析路径不一致。该模式在 --mcp-budget-mode 上已存在(新选项继承了它),且需要 fast-path 回退与重复取值同时发生才会走到 yargs 路径。失效场景:部署模板追加了基础已设置的 flag 时,会在 daemon 整个生命周期内悄悄禁用压力告警,且 wire 上报的 mode 超出声明的联合类型。建议:在 handler 现有校验旁归一化或拒绝(Array.isArray(mode) -> stderr + exit 1),或以最后一个元素为准(若后者生效是预期语义,与 fast path 对齐)。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +232 to +234
// Real process figures; asserted for shape here and for arithmetic in
// the dedicated pressure tests. `toEqual` still fails on an extra key.
pressure: expect.objectContaining({ mode: 'observe' }),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-23: Probe of vitest matcher semantics: toEqual with pressure: expect.objectContaining({ mode: 'observe' }) passes an extra OR missing key nested inside pressure (strictness applies only to runtime.memory's own keys), so this comment ('asserted for shape here ... toEqual still fails on an extra key') is wrong about the assertion it documents. Full-file grep confirms the pinned pressure fields are exactly level/mode/availableBytes/rssRatio; source, ratio, rssBytes, heapUsedBytes, heapRatio, heapLimitBytes have no value assertion anywhere in this file (the key-set test pins names, not wiring) — broader than ledger R2-13's numerator-wiring claim. — Failure scenario: a change that drops, renames, or transposes any of the six unpinned fields ships green, surfacing only as broken SDK consumers; a maintainer trusting this comment believes shape regressions already fail here. Suggested fix: expand the objectContaining to all ten fields with expect.any matchers (or thread a deterministic pressure input and assert exact values), or correct the comment.

中文说明

对 vitest 匹配器语义的探针:pressure: expect.objectContaining({ mode: 'observe' }) 下的 toEqual 对 pressure 内部多出或缺失的嵌套键都会通过(严格性只作用于 runtime.memory 自身的键),因此该注释(「asserted for shape here ... toEqual still fails on an extra key」)对它所描述的断言是错的。全文件 grep 确认被钉住的 pressure 字段恰好是 level/mode/availableBytes/rssRatio;source、ratio、rssBytes、heapUsedBytes、heapRatio、heapLimitBytes 在本文件没有任何值断言(键集测试钉的是名字,不是接线)——范围比台账 R2-13 的 numerator 接线主张更宽。失效场景:删除、重命名或换序六个未钉字段中的任何一个都会绿色发布,只在 SDK 消费者坏掉时暴露;信任该注释的维护者会以为形状回归在这里已经会失败。建议:把 objectContaining 扩到全部十个字段并用 expect.any 匹配(或引入确定性 pressure 输入并断言精确值),或更正注释。

— qwen3.8-max via Qwen Code /review (v0.21.5)

Comment on lines +337 to +340
// Every state a live child can be in, in one response. `sampled` is what
// separates "measured and small" from "never measured": without it, the
// three unreported children below are indistinguishable from children
// using no memory.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R3-24: Mutant run: the suite's docblock claims 'every state a live child can be in', but no contributor has rssBytes: 0 — which the real bridge produces: refreshChildResource stamps childResourceAt even when rssBytes fails validation, and getChildResourceSnapshot returns { rssBytes: info.childRssBytes ?? 0, ageMs } — a fresh, measured-but-zero reading. The truthiness-guard mutant if (!snapshot?.rssBytes) continue passes all 48 tests while a probe with a measured-zero contributor flips (sampled 0 -> 1) — the exact defect class the adjacent ageMs test explicitly warns about. — Failure scenario: a truthiness-guard regression drops measured-zero children from sampled while the suite stays green; clients using the sampled-vs-activeAcpChildren gap signal see a gap that does not exist, and the field's documented 'how many contributed' meaning ships false. Suggested fix: add one zero-rss contributor — e.g. a sixth runtime liveWith(0, 3_000), bumping expectations to rssBytes: 300, sampled: 3, oldestReadingAgeMs: 9_000.

中文说明

变异运行:套件 docblock 声称覆盖「live child 的所有状态」,但没有任何贡献者的 rssBytes 为 0——而真实 bridge 会产生该值:即便 rssBytes 校验失败,refreshChildResource 也会盖 childResourceAt 时间戳,getChildResourceSnapshot 返回 { rssBytes: info.childRssBytes ?? 0, ageMs }——一个新鲜、测得为零的读数。truthiness 守卫变异 if (!snapshot?.rssBytes) continue 能通过全部 48 个测试,而带测得为零贡献者的探针会翻转(sampled 0 -> 1)——正是相邻 ageMs 测试明确警告的缺陷类别。失效场景:truthiness 守卫回归会把测得为零的子进程从 sampled 中丢掉而套件仍绿;使用 sampled 与 activeAcpChildren 差值信号的客户会看到不存在的缺口,字段文档化的「多少个子进程贡献了读数」含义带着错误发布。建议:补一个零 rss 贡献者——例如第六个 runtime liveWith(0, 3_000),期望值改为 rssBytes: 300、sampled: 3、oldestReadingAgeMs: 9_000。

— qwen3.8-max via Qwen Code /review (v0.21.5)

@doudouOUC

Copy link
Copy Markdown
Collaborator Author

Body updated again — #8508 merged into this branch after my last comment, so the description now covers what the branch actually carries.

It is three parts rather than two: Part 1 the daemon root's pressure reading, Part 2 aggregate child RSS, Part 3 the per-child heap partition model from #8508. The old Commit 1 / Commit 2 headings were already wrong (five commits now), so they are Part N throughout. Part 3 brings its own How to verify steps (5–7), its share of Risk & Scope, and Refs #8182 alongside #8051 under Linked Issues.

Two things a reviewer should know about Part 3, both stated in the body:

  • Step 6 is the one that matters — child argv must be byte-identical under --child-heap-mode observe and off. Nothing is applied; limits.memory.enforced stays the required literal false. There is no enforce mode, and step 6 verifies it is genuinely absent rather than merely defaulted off.
  • One review finding is knowingly left open and recorded under Risk & Scope: under off the status snapshot still publishes the modeled partition, so off and observe currently differ only in whether refusals increments. Making off report nothing is a wire-shape decision (it would mean widening maxConcurrentChildren to number | null, since 0 collides with the zero-pool state) and is being taken separately rather than slipped in here.

Also pushed e95dda03e, which fixes two findings against the merged revision, both mutation-verified: the status assertion in run-qwen-serve.test.ts was failing on head (toEqual against a two-field shape while the wire carries four — 217 passed / 1 failed), and decide() ran outside the try that cancels the spawn reservation, so a throwing caller-supplied policy leaked the token.

The red Test (ubuntu-latest, Node 22.x) is still the repo-wide test-isolation collision described in my previous comment, not this PR.

@qwen-code /triage

@doudouOUC
doudouOUC requested review from wenshao and yiliang114 August 5, 2026 02:27
@doudouOUC doudouOUC self-assigned this Aug 5, 2026
@doudouOUC
doudouOUC force-pushed the agent/daemon-memory-observe branch from e95dda0 to dc12afa Compare August 5, 2026 03:02
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

@wenshao

wenshao commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Review

Three observation-only parts on one branch: root memory pressure (runtime.memory.pressure), aggregate child RSS (runtime.memory.children), and a modeled — not applied — per-child heap partition (limits.memory.childHeap). The discipline is unusually good: the sampled denominator, the source: 'unknown' ≠ healthy distinction, null vs 0 for a switched-off model vs an empty pool, warning severity at every level, and the removal of the machinery that only existed to apply the partition (getAcpMemoryArgs(explicitMb?), ChildHeapPoolExhaustedError, the enforced: boolean widening) rather than shipping it unreachable. sampled <= activeAcpChildren holding by construction — same array, same isChannelLive() predicate, one synchronous pass — is the right shape.

Three things worth changing, one of them a real defect.


1. perChildCeilingMb can be modeled below MIN_CHILD_HEAP_MB — via the path the docs recommend

child-heap-policy.ts:

const perChildCeilingMb =
  maxConcurrentChildren > 0
    ? Math.min(
        Math.floor(budget.childPoolMb / maxConcurrentChildren),
        budget.legacyChildCeilingMb,   // <-- not floored at MIN_CHILD_HEAP_MB
      )
    : null;

floor(pool / max) >= MIN_CHILD_HEAP_MB always holds, but legacyChildCeilingMb = min(floor(available * 0.5), 16384) does not, and the Math.min lets it win. When available is in [768, 1023] MB and an operator passes an explicit --memory-budget-mb 1024, the pool clears 512 while the legacy ceiling does not:

avail=768  flag=1024  pool=512  legacyCeil=384  max=1  perChild=384   <-- below 512
avail=900  flag=1024  pool=644  legacyCeil=450  max=1  perChild=450   <-- below 512
avail=1000 flag=1024  pool=744  legacyCeil=500  max=1  perChild=500   <-- below 512
avail=1023 flag=1024  pool=767  legacyCeil=511  max=1  perChild=511   <-- below 512
avail=1024 flag=1024  pool=768  legacyCeil=512  max=1  perChild=512   ok

(Ran against the branch's own createChildHeapPolicy + resolveDaemonMemoryBudget.)

This is not a contrived host. docs/users/qwen-serve.md tells operators on exactly these hosts to "Pass an explicit --memory-budget-mb 1024 on such a host to override the derived figure" — so following the documented remedy is what reaches the band.

What it contradicts:

  • the module's own comment ("A pool below MIN_CHILD_HEAP_MB hosts no child at all" — here it claims one child at 384 MB);
  • ChildHeapPolicySnapshot.perChildCeilingMb's contract, alongside a published minChildHeapMb: 512 in the same snapshot;
  • the test literally named never models a ceiling below the documented minimum, which only exercises the derived path (availableMemoryMb: 1_024, no budgetMb) and so never sees it.

Nothing is applied today, so the impact is a wrong published figure rather than an OOM. But this is the number the PR asks reviewers to judge the partition by, and it is the one an enforce mode would hand to --max-old-space-size. Suggested fix — refuse the model rather than shrink under the floor:

const rawCeiling =
  maxConcurrentChildren > 0
    ? Math.min(Math.floor(budget.childPoolMb / maxConcurrentChildren), budget.legacyChildCeilingMb)
    : null;
const perChildCeilingMb =
  rawCeiling !== null && rawCeiling >= MIN_CHILD_HEAP_MB ? rawCeiling : null;
// and maxConcurrentChildren 0 in that case, for the same reason the zero-pool case reports 0

Please add the budgetMb axis to the parametrized test — every current case goes through the derived path, which is why the mutation sweep did not catch this.

2. An unmeasurable numerator becomes a measured zero

daemon-memory-pressure.ts applies usable() to both denominators and both numerators, then reports the sanitized numerator:

const rssBytes = usable(input.rssBytes);       // NaN / Infinity / negative -> 0
...
return { level: classify(ratio), ratio, source, rssBytes, rssRatio, ... };

So a NaN RSS is published as rssBytes: 0, rssRatio: 0, level: 'normal', source: 'rss' — indistinguishable on the wire from a daemon genuinely using almost nothing. The test treats a %p gauge as unmeasured rather than classifying on it asserts { ratio: 0, level: 'normal' }, which enshrines it.

That is the one place the PR breaks its own doctrine. It goes out of its way to make source: 'unknown' and sampled: 0 unreadable as health, then leaves the numerator with no such marker. Impact today is low — the sole production caller is process.memoryUsage(), which cannot return NaN — but this is a public wire contract, and the docs already invite readers to check rssRatio by hand. Consider treating a non-finite numerator as unmeasured on that side (drop it out of the Math.max and out of source), falling through to source: 'unknown' when neither side is usable.

3. refusals will fire on channel swaps at full occupancy

ProcessRegistry.committedProcessCount deliberately counts a terminating child until it exits, and maxConcurrentChildren clamps to MAX_DAEMON_WORKSPACES (25). On a ≥32 GB host those two meet: maxConcurrentChildren === 25, so a daemon with 25 live children cannot replace a channel without recording a refusal — the replacement's reserve() transiently makes the count 26.

Both halves are individually documented ("its memory is still resident" / "admission pressure"); their interaction is not. Two consequences worth stating in ChildHeapPolicySnapshot.refusals:

  • under observe, a nonzero count on a large host may be pure swap overlap, not capacity pressure — which undercuts the counter's only job;
  • under a future enforce, it means every channel replacement at capacity gets refused, and the daemon cannot recover a workspace it is already running.

Related, smaller: on any host below ~2 GB maxConcurrentChildren is 0, so refusals equals the total ACP spawn count. Correct by the definition, alarming to read. insufficientMemory is already true there, so a sentence pointing at it would be enough.


Smaller notes

  • run-qwen-serve.test.tsexpect(memory?.enforced).toBe(false) now appears twice (once new, once in the pre-existing block below).
  • run-qwen-serve.test.tsperChildCeilingMb: expect.any(Number) is host-dependent: on a runner constrained to ≤512 MB the model correctly returns null and this fails. Given the suite deliberately boots a real daemon, expect.toBeOneOf-style tolerance or an explicit null branch would be more honest than a matcher that assumes the host.
  • bridge.test.ts — the staleness test assigns Date.now directly. The try/finally makes it safe, but vi.spyOn(Date, 'now') / vi.setSystemTime restores on teardown even if the process is torn down mid-assertion, and is what the rest of the repo reaches for.
  • run-qwen-serve.ts samplerrefreshChildResource catches internally and never rejects (it tees to teeServeDebugLine), so the .catch(err => daemonLog.warn(...)) is unreachable. Pre-existing, but the loop now replicates it 25×; if it stays, the message should carry the workspace so a warning would be attributable.
  • 'off' | 'observe' is now hand-written in six places (yargs choices, two fast-path guards, two ServeOptions fields, DaemonStatusMemoryLimits, plus the SDK mirror). ChildHeapMode is exported — reusing it for the child-heap side would leave the SDK mirror as the only intentional restatement.
  • Docs17-configuration.md and 20-quickstart-operations.md write the pressure row as `off` \| `observe` and the child-heap row as `off \| observe`. Both render; pick one.

Performance / security

  • Status now costs one extra process.memoryUsage() per request. The comment explaining why it is not shared with runtime.process is convincing; the metrics ring's 5 s sample was the other option, and a fresh read is the right call for a field whose whole point is "right now".
  • The sampler fan-out is bounded by MAX_DAEMON_WORKSPACES and single-flight per bridge, and only runs while a watcher is attached. Fine.
  • No new surface: two enumerated flags, no user-controlled strings on any path, nothing new logged. enforce being rejected at both yargs and the fast path (asserted in both suites) is the right belt-and-braces.

Test coverage

Genuinely strong, and the mutation list is the kind of evidence that makes a review cheap — the sampled := activeAcpChildren and liveness-gate mutants are exactly the two that matter. The deliberately unfaithful dormant-bridge stub is a good call. Two gaps beyond the ones the PR already declares: the flag-budget axis in §1, and no case where workspaceRegistry is absent but workspaceSnapshots report live channels — there activeAcpChildren > 0 while the sum loop iterates nothing, so sampled: 0 is structural rather than watcher-gated. The invariant still holds and the field docs cover it, but it is the one shape where sampled: 0 cannot ever become nonzero.

Nothing above blocks the observation-only intent. §1 is the one I would fix before merge, since it is the figure this PR exists to publish.

yiliang114
yiliang114 previously approved these changes Aug 5, 2026

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, no blockers. Verified observation-only as intended: decide() return discarded at spawn, argv byte-identical, no spawn refused, no GC/kill added. Multi-workspace accounting holds by construction (same listManaged + isChannelLive gate), denominators handle zero/NaN/Inf, pinned partition math recomputed correct, no security issues (additive fields on auth-gated /daemon/status). One P2 to sign off: default 'observe' flips the top-level /daemon/status rollup ok->warning on uncalibrated thresholds (borrowed from interactive CLI) — a day-one behavior change for anyone alerting on status; consider defaulting 'off' until this phase's data calibrates thresholds, or call out in release notes. Rest is documented intentional phase-1 scoping.

@doudouOUC
doudouOUC enabled auto-merge August 5, 2026 07:57
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 121 passed · 0 failed · 121 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:121 通过 · 0 失败 · 121 总计

Verification report

PR 8423 Deep Verification — feat(serve): observe daemon and child memory against real denominators

Verdict: merge-ready — 121 scripted harness assertions executed, 121 passed, 0 failed; 931 affected unit tests green; 5/5 mutants killed.
Verified head: 4eb1a85f2ec758586d695ef0f07a231689698b64 (merge commit aa8470e, base tip da37110).

中文摘要
  • 结论:merge-ready。全部 121 条脚本化断言通过(wire A/B 48、session 探针 29、模块矩阵 31、变异汇总 13),受影响单元测试 931 个全绿(cli 408 + acp-bridge 523),5 个变异体全部被对应测试杀死。
  • A/B 结论:head 构建的 daemon 在 /daemon/status 中给出 runtime.memory.pressure(十个字段齐全、双分母取劣、source 指名胜出分母、MB→字节换算在 wire 上可对账)、runtime.memory.children(空闲时 sampled=0/rssBytes=0/oldestReadingAgeMs=null,有 SSE 观察者且子进程存活时 sampled=1、rssBytes 约两百 MB 量级)、limits.memory.childHeap(本机 25×5021 ≤ 池 125547);base 构建的同一请求没有 pressure/children/childHeapchildRssCoverage 仍为 primary_only——翻转干净,无多余字段。
  • 「不应用」不变量经双重验证:真实 session 派生的 qwen --acp 子进程 argv 在 observeoff 两种模式下均为宿主推导值 --max-old-space-size=16384不等于建模的 perChildCeilingMb(本机 5021);代码级 grep 确认 decide() 只有一个生产调用点且返回值被丢弃,ChildHeapPoolExhaustedError 不存在。enforce 值在两个 flag 上均被 yargs choices 拒绝;off 模式保留全部读数、仅去掉判定,且分区字段为 null(与零池的 0 区分)。
  • Findings:无阻塞项。两条描述性更正见 Corrections(PR 正文引用的变异失败数略低于实测,方向有利)。
  • 未覆盖:Windows/macOS、真实多子进程(>25)下的 refusals 实弹计数、WS(非 SSE)观察路径、逐提交归因(depth-2 浅克隆)、SDK 类型镜像的编译期校验、文档内容的正确性、全仓测试套件。

Central claim + A/B

Central claim: /daemon/status reports memory observation against real denominators — root pressure (worse of RSS-vs-available and heap-vs-V8-limit), aggregate child RSS with an honest sampled count, and a modeled per-child heap partition that fits the pool — while applying nothing.

Real daemons booted from the head build and from a base build compiled at HEAD^1 (worktree + symlink-farm node_modules; changed workspaces re-pointed into the base tree — readlink -f asserted for acp-bridge, cli, sdk; unchanged deps resolve to head tree, verified untouched by this PR's diff). Identical scenario: boot serve --port 0 --token … --workspace …, poll until runtime.memory exists, GET /daemon/status with bearer auth. Witness: 01-wire-ab-head-vs-base.png.

cell environment oracle result
head-default head build, default flags 27 assertions: boot + runtime ready; 10 pressure fields exactly; ratio == max(rssRatio, heapRatio); source matches winner (heap won live: 0.0201 vs 0.00075); availableBytes == availableMemoryMb × 2²⁰ (253143 MB → 265439674368 B, exact on the wire); thresholds classify; childRssCoverage: active_children; idle children {0, 0, null}; sampled ≤ activeAcpChildren; childHeap {observe, 25, 5021, 0} with 25×5021=125525 ≤ pool 125547; enforced === false; no issue while normal 27/27
base-default base build, default flags 8 assertions: boot + runtime ready; no pressure, no children, childRssCoverage: primary_only, no childHeap key, enforced: false, head = base + exactly {pressure, children} 8/8
head-both-off head build, --memory-pressure-mode off --child-heap-mode off 11 assertions: pressure figures fully populated with mode: off (identical field set), no daemon_memory_pressure issue, partition nulls (maxConcurrentChildren: null, perChildCeilingMb: null) distinct from zero-pool 0, refusals: 0, enforced: false 11/11
head-enforce-* head build, --child-heap-mode enforce / --memory-pressure-mode enforce daemon refuses to boot; stderr carries yargs Invalid values … Choices: "off", "observe" for both flags 2/2

Session-level secondary cells (witness: 02-session-probe-observe.png, 03-session-probe-off.png): a real POST /session spawns a real qwen --acp child against a loopback fake-OpenAI peer; GET /session/:id/events held open as the SSE watch gate:

probe argv oracle sampling oracle result
observe --max-old-space-size=16384 (host-derived), not perChildCeilingMb 5021 with watcher live: children {rssBytes: 215826432, sampled: 1, oldestReadingAgeMs: 934}, activeAcpChildren: 1, sampled ≤ active, partition still published, refusals: 0 15/15
off argv byte-identical: --max-old-space-size=16384 sampling still live (rssBytes: 223891456, sampled: 1), partition suppressed to nulls 14/14

The pure-function matrix (witness: 04-module-matrix.png, 31/31 against real dist code) drove the boundaries the live daemon cannot reach: threshold edges 0.4999/0.5/0.6499/0.65/0.7999/0.8; source selection incl. exact tie → rss; availableBytes=0 → heap-only; both zero → unknown/normal/0 never critical; NaN/−5/Infinity sanitize to 0; PR-pinned partitions 3687→7×526 and 15360→25×614 (the 25 proven to be the MAX_DAEMON_WORKSPACES cap biting over an uncapped 30); zero-pool 256→{0, null} (the fixed defect — never a 0 ceiling); 1 TB pool ceiling capped at legacy 16384; invariant sweep over 10 pool sizes; decide() refusal counting; off mode inert.

Verified along the way (not findings)

  • Bootstrap window is real and matches the code comment. The first status fetch after listening serves the bootstrap app: limits.memory populated with childHeap: null, runtime.memory absent, runtime.loading: true — observed on every boot, both arms. The PR's own comment warns against "budget resolved implies pressure present"; the warning is accurate.
  • source: heap on this host is the worse-of-two logic working as advertised (V8 limit 4.05 GB ≪ 247 GB host), with rss direction covered at module level.

Corrections

Two descriptions in the PR body understate the measured mutation counts — the direction is favorable (more coverage than claimed), but the record should match the measurement:

  1. "Math.maxMath.min on the pressure ratio fails 7" — measured 9 across the two affected suites (7 in daemon-memory-pressure.test.ts, 2 in daemon-status.test.ts). The claimed 7 matches the pressure suite alone.
  2. "dropping the MB→bytes conversion fails 2 tests" — measured 3 in daemon-status.test.ts, one of them named exactly converts the budget from megabytes when computing the ratio (fails expected 4096 to be 4294967296, the 2²⁰ factor).

These are corrections to the description, not requests to change code.

Findings

None blocking. Every executed assertion passed; no behavioral mismatch was produced by any cell, probe, or mutant.

Not covered

  • Windows / macOS — this round ran on the lane's node:22-bookworm container (Linux). The PR's stated platform-sensitive decision (child self-reported RSS instead of /proc) was not exercised on the other platforms; they ride on the repo's CI matrix.
  • refusals under real admission pressure — counting >maxConcurrentChildren live children would need 26 concurrent sessions; covered instead by module matrix decide() cells and the unit suite's wiring tests. This reproduces the refusal logic, not the admission load that would feed it.
  • WS watch path — the sampler gate is sseCount > 0 || wsStreams > 0; only the SSE half was driven live.
  • oldestReadingAgeMs with pre-field contributors — the "absent on older bridges" branch is unit-covered; a genuinely older bridge was not constructed live.
  • Per-commit attribution — the checkout is depth 2 (git rev-list HEAD^1..HEAD^2 yields only the head OID; the metadata lists 6 commits). The aggregate HEAD^1..HEAD diff is what was verified; the 6 commit messages were treated as untrusted description.
  • SDK type mirror (packages/sdk-typescript/src/daemon/types.ts) — type-only changes, verified by reading and by the workflow's successful full build, not by a separate SDK compile gate or a mixed-version daemon/client pairing.
  • Docs — six documentation files changed; content not audited beyond the childRssCoverage/childHeap claims that the wire oracle settled independently.
  • Repo-wide suite / lint / typecheck — targeted gates only (affected suites of packages/cli + packages/acp-bridge). The workflow's pre-run npm run build at head is the compile evidence.
  • Base-arm npm ci was not needed: unchanged workspaces reuse the head tree's already-built dist (diff-verified untouched); the three changed workspaces were rebuilt from base source. Stated per the environment contract so the control's purity is auditable.

Methodology

Environment: GitHub-runner container (the verify lane itself), Node v22.23.2, ~247 GB host memory (availableMemoryMb 253143, cgroup unconstrained → availableMemorySource: host), V8 heap_size_limit 4345298944 B. Base control: git worktree tmp/base-tree HEAD^1 with a symlink-farm node_modules re-pointing @qwen-code/{acp-bridge,qwen-code,sdk} into the base tree (realpath-asserted) and nested per-package node_modules mirrored; acp-bridge + sdk + cli rebuilt there (core etc. copied from head, identical per diff), then removed after the A/B cells were captured. Harnesses (all mock-free, in harness/): boot-status.mjs boots a real daemon and captures /daemon/status over real HTTP; wire-ab.mjs orchestrates the five cells above; session-probe.mjs adds a loopback fake-OpenAI peer, a real POST /session child spawn, argv extraction by PPID, and a held SSE stream; module-matrix.mjs drives the compiled modules directly; mutation-summary.mjs re-asserts each mutant's kill from the recorded logs plus the unmutated positive controls. Five mutants were applied to head source, their suites run red on the intended behavioral assertions (quoted in logs mut1…mut5), and every file restored (git status clean afterward). Session probes were run twice (once under the image capture, once for text logs); absolute byte readings vary between runs (~180–224 MB here) while every assertion held on both runs. Raw per-cell payloads: logs/cells/*.json; gate logs: logs/gates-*.log; A/B log: logs/wire-ab.log.

Evidence images

01-wire-ab-head-vs-base

02-session-probe-observe

03-session-probe-off

04-module-matrix

05-mutation-matrix

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Gate re-run on request — the head is unchanged since the last pass (a909ff5a), so this is a re-attestation on the same commit rather than a delta review; every check below was re-run.

Moving on to code review. 🔍

中文说明

应请求重跑门禁——head 与上次通过时相同(a909ff5a),因此这是对同一提交的重新确认而非增量审查;下列每一项检查均已重新执行。

进入代码审查。🔍

Qwen Code · qwen3.8-max

Reviewed at a909ff5a1414f128af7dc732efa9562aae85299d · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Code review

Re-run on request at an unchanged head — no code delta since the last review, so this pass re-verified every prior finding in the tree rather than trusting the record. All confirmed.

The observation-only invariants, re-checked directly: decide() has exactly one production call site (spawnChannel.ts), its answer is discarded — reserve() before decide(), decide() inside the try whose catch cancels the reservation, and the child argv still receives only the host-derived getAcpMemoryArgs(). limits.memory.enforced is the literal false; yargs choices and the fast path both reject enforce on --memory-pressure-mode and --child-heap-mode, pinned by tests. The sub-floor gate still refuses the model rather than shrink under it: modelable = rawCeilingMb !== null && rawCeilingMb >= MIN_CHILD_HEAP_MB, with maxConcurrentChildren zeroed in lockstep with the null ceiling, and the budgetMb test axis plus the inclusive 1024/1024 boundary keeping both the defect and the lazy fix unable to pass. The children sum and its sampled count come from one synchronous pass gated on the same isChannelLive() predicate, so sampled <= activeAcpChildren holds by construction. Pressure reports the worse of the two denominators with source naming the winner, numerators and denominators sanitized separately, and the raised issue stays warning at every level including critical so the top-level rollup is untouched.

The three earlier findings remain fixed or resolved as last described: sub-floor ceiling (refused, not shrunk), non-finite numerator (usableNumerator retires the side instead of publishing a measured zero), and swap-time refusals (documented on both the snapshot type and the wire mirror rather than netted out).

Smaller items, carried, none blocking: the daemon-memory-pressure.ts JSDoc still says "the sampler currently reads the primary ACP child alone" — stale since Part 2's fan-out; the bootstrap comment in run-qwen-serve.ts still says "even when the flag says enforce", which the flag can no longer say. And F1 stays with the author: test-plan step 4 in the PR body describes a per-workspace gate (sampled: 1 of 2), but the sampler gate is daemon-global — the repro as written produces sampled: 2, which is correct behaviour; the shipped docs describe the gate correctly, only the PR-body instruction is wrong.

Test evidence

Unattended CI run: I have not built or executed this PR's code — the evidence below is the PR's own CI on the reviewed commit, read through the API, plus the two signals named after the table. No terminal (tmux) capture on this path; live behaviour is exercised only by the isolated jobs named below.

All four pull_request-event workflows on this head completed green — nothing pending — including the leg that was red on an earlier head (the unrelated packages/core glob timeout, deflaked upstream rather than by this PR). The macOS, Windows, and integration legs remain skipped by repo configuration, as on every head of this PR.

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Real daemon E2E / Java 11 ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Post Coverage Comment (ubuntu-latest, 22.x) ✅ success
Classify PR · label · route · authorize ✅ success
Test (macos-latest, Node 22.x) ⏭️ skipped
Test (windows-latest, Node 22.x) ⏭️ skipped
Integration Tests (CLI, No Sandbox) ⏭️ skipped
build-cli ⏭️ skipped

Two further signals, both stronger than CI for this PR:

  • Maintainer live verification (wenshao, on this exact head) — reported in the thread above: 11 real qwen serve daemons on Linux, seven of them inside cgroups down to 240 MB; all seven Reviewer Test Plan steps pass, including both review-driven edge cases and the byte-identical child-argv check under observe vs off; a mutation sweep kills every guard the PR claims, with the honest note that the zero-pool clamp is subsumed by the sub-floor guard. His verdict: merge-ready, with F1 as a description fix and F2/F3 as accepted follow-ups. Not re-run by me — attributed as his report.
  • Sandboxed verification. The two completed /verify runs passed on the two prior heads (4eb1a85f: 121 assertions; e8aafa66: 122 — A/B against the base build, mock-free harnesses, mutants killed). The Aug 6 attempt covering this head was cancelled before posting a report; a fresh attempt is in flight right now in the same run as this triage pass and will post its report to its marker comment. Platform coverage stays Linux-only in all of this; macOS/Windows rest on the skipped CI legs above — a stated caveat of the PR, mitigated by the one platform-sensitive decision being deliberate (child RSS comes from each child's self-report, not /proc).

Sandboxed verification of the one claim CI cannot settle — that the observe/off wire fields behave as specified on a live daemon and nothing is applied to child argv — is already covered by the in-flight @qwen-code /verify run for this head (maintainer-triggered); no new trigger needed.

中文说明

代码审查

应请求在不变的 head 上重跑——自上次评审以来无代码增量,因此本次直接在树中复核了此前每一项发现,而非依赖记录。全部确认无误。

仅观测不变量,逐一直接复查:decide() 只有一个生产调用点(spawnChannel.ts),其结果被丢弃——先 reserve()decide()decide() 位于 catch 会取消预留的 try 之内,子进程 argv 依然只接收宿主推导的 getAcpMemoryArgs()limits.memory.enforced 是字面量 false;yargs choices 与 fast path 都拒绝两个 flag 传入 enforce,且有测试钉住。低于下限的门控仍是拒绝建模而非压缩上限:modelable = rawCeilingMb !== null && rawCeilingMb >= MIN_CHILD_HEAP_MBmaxConcurrentChildrennull 上限同步归零,budgetMb 测试维与 1024/1024 闭区间边界使缺陷本身与偷懒修法都无法通过。子进程 RSS 汇总与其 sampled 计数出自同一次以 isChannelLive() 门控的同步遍历,sampled <= activeAcpChildren 由构造保证。压力读数取两个分母中更劣者、source 指名胜出方,分子与分母分别净化,且所抛 issue 在所有级别(含 critical)均为 warning,顶层 rollup 不受影响。

此前的三项发现维持上次结论:低于下限的上限(拒绝而非压缩)、非有限分子(usableNumerator 让该侧整体退出而非发布「测得的零」)、满载换代时的 refusals(在快照类型与线上类型镜像上文档化,而非做净额处理)。

较小的遗留项,继续携带,均不阻塞:daemon-memory-pressure.ts 的 JSDoc 仍写着「采样器目前只读取主 ACP 子进程」——Part 2 扇出之后已过时;run-qwen-serve.ts 的引导注释仍写「即便 flag 写着 enforce」——该取值现已无法通过任何路径传入。F1 仍留给作者:PR 正文测试计划第 4 步描述的是按 workspace 的门控(2 个中 sampled: 1),但采样门控是 daemon 全局的——按其字面复现只会得到 sampled: 2,而这是正确行为;随 PR 发布的文档对门控的描述都是对的,只有 PR 正文的这条说明写错了。

测试证据

无人值守 CI 运行:我没有构建或执行本 PR 的代码——下面的证据是通过 API 读取的本 PR 自身在受审提交上的 CI,以及表格之后列出的两项信号。此路径无终端(tmux)捕获;真实行为仅由下面点名的隔离任务执行。

本 head 上四条 pull_request 事件的工作流全部完成且为绿——无待决项——包括早期 head 上变红的那条腿(无关的 packages/core glob 超时,由上游去 flake 而非本 PR 修复)。macOS、Windows 与集成腿仍被仓库配置跳过,与本 PR 此前每个 head 相同。

另两项信号,对本 PR 而言都比 CI 更强:

  • 维护者实测(wenshao,在本 head 上) —— 见上方线程中的报告:Linux 上 11 个真实 qwen serve daemon,其中 7 个在 cgroup 内、最低 240 MB;Reviewer Test Plan 七步全部通过,包括两个由评审驱动的边界情形与 observe/off 下逐字节一致的子进程 argv 检查;变异扫描杀掉了 PR 声称的每个守卫,并诚实记录零池钳位被低于下限的守卫所包含。其结论:可合入,F1 为描述修复,F2/F3 为已接受的后续项。我未重跑——如实标注为他的报告。
  • 沙箱验证。 此前两次已完成的 /verify 在之前的两个 head 上通过(4eb1a85f:121 条断言;e8aafa66:122 条——与 base 构建 A/B 对照、无 mock harness、变异体全部被杀)。8 月 6 日覆盖 head 的尝试在发布报告前被取消;一次新的尝试正在本次 triage 所在的同一运行中进行,报告会发布到其标记评论。以上一切的平台覆盖仍仅 Linux;macOS/Windows 依赖上述被跳过的 CI 腿——这是 PR 明示的 caveat,缓解在于唯一的平台敏感决策是刻意为之(子进程 RSS 取自各自自上报,而非 /proc)。

CI 无法钉住的那个行为性论断——observe/off 线上字段在真实 daemon 上按描述工作、且没有任何东西被应用到子进程 argv——其沙箱验证已由本 head 上进行中的 @qwen-code /verify 运行覆盖(维护者触发),无需新的触发。

Qwen Code · qwen3.8-max

Reviewed at a909ff5a1414f128af7dc732efa9562aae85299d · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — still clean at this head: every prior finding remains fixed or resolved-by-documentation, all four PR-CI workflows are green with nothing pending, and a maintainer has approved this exact commit after verifying it against real daemons — but the cross-package core-size escalation caps the bot at 3/5, so this stays a maintainer merge call by policy, not by doubt.

What this pass adds, on an unchanged head (a909ff5a — no code delta since the last review):

  • Re-verified the load-bearing invariants in the tree rather than trusting the last pass: decide() has one production call site and its answer is discarded (reserve before decide, cancel-on-throw, argv untouched); limits.memory.enforced is the literal false; yargs and the fast path both reject enforce on either flag; the sub-floor gate still refuses the model rather than shrink under it; the children sum and its sampled count come from one synchronous pass over the same live-channel predicate. All as described in the Stage 2 note.
  • All four pull_request-event workflows on this head are completed and green (Qwen Code CI, Serve A/B, Qwen Live Host CI, SDK Java); nothing pending.
  • The sandboxed /verify for this head is in flight in the same run as this triage pass — the Aug 6 attempt was cancelled before it could post a report. The completed ✅ runs on record cover the two prior heads (4eb1a85f, e8aafa66); this head's report will land on its marker comment.
  • wenshao's approvals stand on this exact commit; his live verification (eleven real daemons, seven under cgroups, all seven test-plan steps, mutation sweep) is on the thread above.

Why the bot still does not approve: this is a cross-package core change at 907 production lines, which Stage 0 escalates for maintainer awareness and caps at 3/5 regardless of review outcome — the approval stays a human's to give. The housekeeping note from the last pass also stands, re-checked this time against the review body itself: the CHANGES_REQUESTED state showing on this PR is the bot's own /review-round-2 from Aug 3 (commit 09155029), whose only Critical was the template blocker the author resolved on Aug 4 — the body now carries every required heading in both languages. GitHub reviews cannot be edited, and re-run policy neither stacks nor dismisses reviews, so it stays until a maintainer dismisses it.

⏸️ Deferring to @wenshao — everything the gate can check is checked and green at this head, including your own live verification and approvals. Once the in-flight sandboxed verify lands beside the green CI, the one remaining step is dismissing the stale review state, and this is ready to merge.

中文说明

置信度:3/5 —— 本 head 依然是干净的:此前所有发现或已修复、或以文档化解决,四条 PR CI 工作流全绿且无待决项,且一位维护者在真实 daemon 上验证后已批准了这个提交——但跨包核心模块的规模升级使机器人的置信度上限为 3/5,因此这仍是维护者的合并决策,是政策使然,而非心存疑虑。

在不变的 head(a909ff5a——自上次评审以来无代码增量)上,本次补充:

  • 不依赖上次结论,直接在树中复核了关键不变量:decide() 只有一个生产调用点且结果被丢弃(先预留后决策、抛错取消预留、argv 不变);limits.memory.enforced 是字面量 false;yargs 与 fast path 都拒绝两个 flag 传入 enforce;低于下限的门控仍是拒绝建模而非压缩上限;子进程 RSS 汇总与其 sampled 计数出自同一次以同一存活通道谓词门控的同步遍历。均与 Stage 2 所述一致。
  • 本 head 上四条 pull_request 事件的工作流全部完成且为绿(Qwen Code CI、Serve A/B、Qwen Live Host CI、SDK Java),无待决项。
  • 本 head 的沙箱 /verify 正在本次 triage 所在的同一运行中进行——8 月 6 日的尝试在发布报告前被取消。记录在案的两份 ✅ 报告覆盖此前两个 head(4eb1a85fe8aafa66);本 head 的报告将发布到其标记评论。
  • wenshao 的批准在这个提交上有效;他的实测(11 个真实 daemon、7 个在 cgroup 内、测试计划七步、变异扫描)见上方线程。

机器人仍不批准的原因:这是一次 907 行生产代码的跨包核心改动,Stage 0 将其升级为维护者关注,且无论审查结果如何置信度上限都是 3/5——批准由人来给。上次的事务性提醒依然成立,且本次已对照评审正文复核:本 PR 当前显示的 CHANGES_REQUESTED 状态来自机器人 8 月 3 日的 /review 第二轮评审(提交 09155029),其唯一 Critical 是作者已于 8 月 4 日解决的模板拦截——PR 正文现已包含双语的所有必需标题。GitHub 评审无法编辑,重跑政策既不叠加也不撤销评审,因此它将一直保留,直到维护者将其驳回。

⏸️ 转交 @wenshao —— 门禁能查的一切在本 head 上均已查毕且为绿,包括你自己的实测与批准。待进行中的沙箱验证与绿色 CI 并立落地,剩下的唯一步骤就是驳回陈旧的评审状态,届时即可合入。

Qwen Code · qwen3.8-max

Reviewed at a909ff5a1414f128af7dc732efa9562aae85299d · re-run with @qwen-code /triage

@qwen-code-ci-bot
qwen-code-ci-bot dismissed their stale review August 5, 2026 08:41

Stage-1a template gate is now satisfied: the PR body carries every required heading (What this PR does / Why it's needed / Reviewer Test Plan with How to verify, Evidence, Tested-on / Risk & Scope / Linked Issues) plus the Chinese translation. Dismissing this run's own stale gate review so the outstanding CHANGES_REQUESTED state reflects code findings only (see the stage-3 triage comment).

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot has neither a verdict nor a deferral on 4eb1a85f2ec758586d695ef0f07a231689698b64 — no APPROVED, CHANGES_REQUESTED, or COMMENTED review of its own. A DISMISSED one does not count: dismiss_stale_reviews voids the bot's approval on every push, which is exactly when a fresh one is needed. If this re-run was meant to review or approve, it did not, and an approval left by another account is a separate vote that does not count as the bot's own.

⚠️ 机器人在 4eb1a85f2ec758586d695ef0f07a231689698b64既没有裁决也没有 defer —— 没有属于它自己的 APPROVEDCHANGES_REQUESTEDCOMMENTED 评审。DISMISSED 不算:dismiss_stale_reviews 会在每次推送时作废机器人的批准,而那恰恰是需要一次新批准的时刻。如果这次重跑本应评审或批准,那么它没有做到;而其他账号留下的批准是另一张票,不能算作机器人自己的。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Not reviewed: build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未审查:build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +48 to +51
import {
createChildHeapPolicy,
type ChildHeapPolicy,
} from '@qwen-code/acp-bridge/childHeapPolicy';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R5-1: The new @qwen-code/acp-bridge/childHeapPolicy subpath import (also used by daemon-status.test.ts) has no source alias in packages/cli/vitest.config.ts, unlike the sibling acp-bridge subpaths the config aliases to .ts source. npm ci runs prepare → build, so nothing fails in CI today — but the config's own comment says the aliases exist because dist may be stale or absent during development. — Failure scenario: with a stale packages/acp-bridge/dist/ present, this import executes the compiled pre-edit copy while aliased subpaths read live source, so one suite run tests two different module copies (probe-verified: with dist removed the import fails collection; with the alias added it resolves to source).

// packages/cli/vitest.config.ts — add beside the sibling aliases:
'@qwen-code/acp-bridge/childHeapPolicy': path.resolve(
  __dirname,
  '../acp-bridge/src/child-heap-policy.ts',
),
中文说明

新的 @qwen-code/acp-bridge/childHeapPolicy 子路径导入(daemon-status.test.ts 中同样使用)在 packages/cli/vitest.config.ts 中没有源码别名,而该配置为其他 acp-bridge 子路径都配置了指向 .ts 源码的别名。npm ci 会执行 prepare → 构建,因此 CI 目前不会失败——但配置自身的注释说明这些别名存在的原因是「dist 在开发期间可能过期或缺失」。失效场景:当存在过期的 packages/acp-bridge/dist/ 时,该导入会执行编译出的旧副本,而配置了别名的子路径读取的是实时源码,导致同一次测试运行针对两份不同的模块副本(已用探针验证:移除 dist 后该导入收集失败;添加别名后解析到源码)。

— qwen3.8-max via Qwen Code /review (v0.21.6)

`limits.memory` is additive and reports the daemon's resolved memory figures: a required `enforced: false`, `configuredBudgetMb`, `effectiveBudgetMb` (the configured value capped at resolved cgroup/host memory), `budgetSource` (`flag` / `derived`), `availableMemoryMb`, `availableMemorySource` (`constrained` / `host`), `insufficientMemory`, and a `modeled` object holding `rootReserveMb`, `childPoolMb`, `minChildHeapMb`, `maxChildHeapMb`, and `legacyChildCeilingMb` (a conservative model of the ceiling an ACP child receives today, which can sit below the real figure). `runtime.memory` additionally reports `registeredWorkspaces` (the registration count — non-removed workspace entries, including draining, transitioning, or blocked ones; not a live-child count), `activeAcpChildren` (daemon-managed ACP children with a live, non-dying channel — includes transitioning or blocked entries, but excludes a workspace whose kill has started even if the child has not exited; not channel workers, MCP descendants, or unattached spawn reservations), `childRssCoverage` (`primary_only` today), and a `modeled` object holding `recommendedShareAtRegisteredMb` (`null` when no workspace is registered) and `recommendedShareAtActiveMb` (`null` when no child is active). Each share is capped at the legacy child ceiling, and floored at the minimum child heap only when the ceiling allows — on a small host the ceiling sits below the floor, so share × count can exceed the child pool. Read a share as advisory, not a partition of the pool. All of it is observation: no child spawn argument derives from these values, and no request is refused on their basis. On the normal `runQwenServe` path the budget is resolved before the bootstrap app is created, so `limits.memory` is already populated during the bootstrap window. It is `null` only on paths that resolve no budget (such as direct-embed bypassing `runQwenServeImpl`). The SDK type allows `null`, so correct clients cope.
`limits.memory` is additive and reports the daemon's resolved memory figures: a required `enforced: false`, a `childHeap` object (`mode`; `maxConcurrentChildren` and `perChildCeilingMb`, both `null` under `mode: 'off'`, which models nothing — and `perChildCeilingMb` additionally `null` when no child is admissible, never 0, while `maxConcurrentChildren` is `0` in that case, since a pool too small to host one child is a computed answer rather than an absent model; and `refusals`, the spawns that would have exceeded the modeled limit), `configuredBudgetMb`, `effectiveBudgetMb` (the configured value capped at resolved cgroup/host memory), `budgetSource` (`flag` / `derived`), `availableMemoryMb`, `availableMemorySource` (`constrained` / `host`), `insufficientMemory`, and a `modeled` object holding `rootReserveMb`, `childPoolMb`, `minChildHeapMb`, `maxChildHeapMb`, and `legacyChildCeilingMb` (a conservative model of the ceiling an ACP child receives today, which can sit below the real figure). `runtime.memory` additionally reports `registeredWorkspaces` (the registration count — non-removed workspace entries, including draining, transitioning, or blocked ones; not a live-child count), `activeAcpChildren` (daemon-managed ACP children with a live, non-dying channel — includes transitioning or blocked entries, but excludes a workspace whose kill has started even if the child has not exited; not channel workers, MCP descendants, or unattached spawn reservations), `childRssCoverage` (`active_children` — every ACP child with a live channel, which is the set `activeAcpChildren` counts; older daemons send `primary_only`), a `children` object described below, and a `modeled` object holding `recommendedShareAtRegisteredMb` (`null` when no workspace is registered) and `recommendedShareAtActiveMb` (`null` when no child is active). Each share is capped at the legacy child ceiling, and floored at the minimum child heap only when the ceiling allows — on a small host the ceiling sits below the floor, so share × count can exceed the child pool. Read a share as advisory, not a partition of the pool. All of it is observation: no child spawn argument derives from these values, and no request is refused on their basis. `childHeap` models a fixed partition of `modeled.childPoolMb` — every child would receive the same `perChildCeilingMb`, so the modeled total stays inside the pool rather than accumulating as a per-spawn share would. Read `refusals` as admission pressure only: a count of 0 does **not** mean the partition is safe to apply, because children run on the much larger host-derived ceiling, so a workload needing more old space than `perChildCeilingMb` is healthy here and would only fail once the partition were applied. On the normal `runQwenServe` path the budget is resolved before the bootstrap app is created, so `limits.memory` is already populated during the bootstrap window. It is `null` only on paths that resolve no budget (such as direct-embed bypassing `runQwenServeImpl`). The SDK type allows `null`, so correct clients cope.

`runtime.memory.children` is additive within that block and reports aggregate RSS across the children `childRssCoverage` names: `rssBytes` (their summed self-reported RSS), `sampled` (how many produced a reading), and `oldestReadingAgeMs` (the age of the oldest reading in the sum, so a caller can tell how far apart its parts were taken). The denominator for `sampled` is the sibling `activeAcpChildren`, not repeated inside the block; when `sampled` is lower, `rssBytes` is a floor rather than a total. Sampling is gated on an active SSE/WS watcher, so a status request against a daemon nobody is streaming from reports `sampled: 0` even with live children — `activeAcpChildren` beside it makes that gap visible, and `rssBytes: 0` with `sampled: 0` never means a measured zero. `oldestReadingAgeMs` is `null` when nothing was sampled and also when every contributor is a bridge predating the field, so it never means "fresh". Read the sum as an over-count and an under-count at once: summing per-process RSS double-counts pages the children share, while each child reports only its own process, so its MCP descendants and every channel worker are missing. It is not the daemon tree's memory. The field is optional in the SDK mirror because daemons reporting `primary_only` never send it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R5-2: This paragraph's unconditional claim — "a status request against a daemon nobody is streaming from reports sampled: 0 even with live children" — ignores the staleness window: refresh stops when the last SSE/WS watcher detaches, but each cached reading survives until it ages out (STALE_CHILD_RESOURCE_MS = 30_000, bridge.ts). — Failure scenario: a client polling /daemon/status within ~30 s of the last watcher disconnecting observes sampled > 0 with zero active watchers, contradicting this sentence; a monitor asserting the documented invariant fails, and an operator reads a stale sum as live coverage. The childRssCoverage JSDoc and the SDK mirror in this same PR ("each reading persists until it ages out (~30s)") contradict it too. Suggested rewording: "…so once the last watcher's cached readings age out (~30 s), a status request against a daemon nobody is streaming from reports sampled: 0 even with live children…"

中文说明

本段无条件表述——「对没有人在流式传输的 daemon 发起 status 请求,即使有活跃子进程也会报告 sampled: 0」——忽略了陈旧窗口:当最后一个 SSE/WS 观察者断开时刷新即停止,但每个缓存读数会存活到过期(STALE_CHILD_RESOURCE_MS = 30_000bridge.ts)。失效场景:客户端在最后一个观察者断开后约 30 秒内轮询 /daemon/status,会在没有任何活跃观察者时观察到 sampled > 0,与本句矛盾;对该文档化不变量做断言的监控会失败,运维也会把过期的求和误读为实时覆盖。同一 PR 中 childRssCoverage 的 JSDoc 与 SDK 镜像(「每个读数会保留到过期(约 30 秒)」)也与之矛盾。建议改为:「……因此在最后一个观察者的缓存读数过期(约 30 秒)后,对没有人在流式传输的 daemon 发起 status 请求,即使有活跃子进程也会报告 sampled: 0……」

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +2061 to +2066
expect(memory?.childHeap).toEqual({
mode: 'observe',
maxConcurrentChildren: expect.any(Number),
perChildCeilingMb: expect.any(Number),
refusals: 0,
});

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R5-3: Probe-verified: this boot test's assertion is vacuous with respect to the policy→factory wiring — removing childHeapPolicy from all three createSpawnChannelFactory call sites (run-qwen-serve.ts:3606/4223/4747) keeps every suite green. The snapshot this assertion reads (managedChildHeapPolicy) exists independently of the factories, the boot uses maxSessions: 1 with a cap ≥ 1, and refusals is 0 with or without the wiring. — Failure scenario: a refactor that drops the wiring ships green, and a production daemon publishes the modeled partition with a refusal counter that can never move — silently zeroing the figure the docs call this mode's product. This is distinct from, and stronger than, the open R3-5 comment, whose premise (that the primary path is exercised by a test that could observe refusals moving) this probe measured false. Suggested fix: boot once more with a budget that models exactly one child (memoryBudgetMb: 1024 → pool 768), create two sessions so two children spawn, and assert childHeap.refusals >= 1 while both sessions still work (observe mode refuses nothing).

中文说明

经探针验证:该启动测试的断言对「策略→工厂」的接线是空洞的——把 childHeapPolicy 从全部三处 createSpawnChannelFactory 调用点(run-qwen-serve.ts:3606/4223/4747)移除后,所有套件依然绿色。该断言读取的快照(managedChildHeapPolicy)独立于工厂而存在;启动使用 maxSessions: 1 且上限 ≥ 1,无论接线是否存在 refusals 都是 0。失效场景:某次重构删除接线后会绿色发布,生产 daemon 将发布那份建模分区,但其拒绝计数永远不会移动——把文档称为该模式全部产出的数字悄悄归零。这与未解决的 R3-5 评论不同且更强:R3-5 的前提(主路径被某个能观察到 refusals 变化的测试覆盖)被本探针证伪。建议修复:再以「恰好建模一个子进程」的预算启动一次(memoryBudgetMb: 1024 → 池 768),创建两个会话使两个子进程派生,并在两个会话仍正常工作时断言 childHeap.refusals >= 1(observe 模式不会真的拒绝任何东西)。

— qwen3.8-max via Qwen Code /review (v0.21.6)

doudouOUC and others added 4 commits August 6, 2026 11:45
…imum

`perChildCeilingMb` is `min(floor(pool / maxConcurrentChildren),
legacyChildCeilingMb)`. The first term is at least `MIN_CHILD_HEAP_MB` by
construction; the second is `floor(available / 2)` and is not, so the
`Math.min` could publish a ceiling *below* the `minChildHeapMb` sitting beside
it in the same snapshot:

    avail=768  --memory-budget-mb 1024  pool=512  legacyCeil=384  perChild=384
    avail=1023 --memory-budget-mb 1024  pool=767  legacyCeil=511  perChild=511

Unreachable from a derived budget — the pool reaches 0 first — but an explicit
budget has a floor of 1024 while available memory does not, and
`docs/users/qwen-serve.md` tells operators on exactly these hosts to pass that
flag. The documented remedy is what reaches the band.

Refuse the model rather than shrink under the floor, with
`maxConcurrentChildren` zeroed in lockstep: a ceiling no child may run at is
not a partition, and "one child fits" beside a null ceiling is the same
contradiction from the other side. Nothing is applied today so the impact was a
wrong published figure, but this is the number the partition asks to be judged
by and the one an `enforce` mode would hand to `--max-old-space-size`.

The existing matrix resolves derived budgets only, which is why the mutation
sweep came back clean; add the `budgetMb` axis, asserting in each case the
shape that makes it reachable, and pin the inclusive boundary (1024/1024 ->
one child at 512) so nulling unconditionally cannot pass instead.

Also, in the same review pass:

- Split usable-gauge handling into numerator and denominator. Coercing an
  unusable numerator to 0 published `rssBytes: 0, rssRatio: 0, level: 'normal',
  source: 'rss'` — a daemon that measured nothing, indistinguishable from an
  idle one, which is the confusion `source: 'unknown'` and `sampled: 0` exist
  to prevent everywhere else here. An unusable numerator now retires its own
  side. Zero stays a reading for a numerator and not for a denominator.
- Document that `rssRatio` divides by host total under
  `availableMemorySource: 'host'`, so it is a lower bound on real pressure
  there — a denominator problem no threshold calibration addresses.
- Document that `refusals` counts channel swaps at full occupancy (the
  terminating child is counted until it exits) and equals the total spawn count
  on a host too small to model a partition. Deliberately not fixed by giving
  the comparison swap headroom, which would admit a 26th ceiling against a
  25-child pool.
- Keep the sampler's rejection handler as a documented backstop — the shipped
  `refreshChildResource` never rejects, but it is an optional `async` interface
  member, so a foreign implementation throwing early would otherwise surface as
  an unhandled rejection — and give it the workspace so it is attributable
  across the fan-out.
- Test hygiene: drop a duplicated `enforced` assertion; replace a host-
  dependent `expect.any(Number)` with a key-set pin plus a branch, since a
  small host now legitimately reports no partition; use `vi.spyOn(Date, 'now')`
  over direct assignment; reuse the exported `ChildHeapMode` on the child-heap
  side, leaving the independent `memoryPressureMode` switch alone.

Reported by @wenshao.

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

doudouOUC commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks — both passes worked through. Answering them together since they overlap.

Nine items taken, one pushed back on, one deferred with a reason. Every fix below is mutation-verified: the new assertion fails against the pre-fix code, and where the fix could be satisfied by over-correcting, a paired assertion fails against that too.


The defect — perChildCeilingMb below minChildHeapMb (review 1 §1, review 2 §3)

Confirmed, and your arithmetic reproduces exactly. Ran your table against the branch's own resolver:

avail=768  flag=1024  pool=512  legacyCeil=384  max=1  perChild=384   <-- below 512
avail=900  flag=1024  pool=644  legacyCeil=450  max=1  perChild=450   <-- below 512
avail=1000 flag=1024  pool=744  legacyCeil=500  max=1  perChild=500   <-- below 512
avail=1023 flag=1024  pool=767  legacyCeil=511  max=1  perChild=511   <-- below 512
avail=1024 flag=1024  pool=768  legacyCeil=512  max=1  perChild=512   ok

You are also right about why it survived: it.each([2048, 8192, 32768, 262144]) resolves derived budgets only, where the pool hits 0 before the legacy cap can drop under the floor. The two paths only separate once an explicit budget is involved, and that is the path docs/users/qwen-serve.md recommends on exactly these hosts. A matrix that cannot reach the band is why the mutation sweep came back clean.

Took your fix — refuse the model rather than shrink under the floor — with maxConcurrentChildren zeroed in lockstep, since "one child fits" beside a null ceiling is the same contradiction from the other side:

const rawCeilingMb = admissible > 0
  ? Math.min(Math.floor(budget.childPoolMb / admissible), budget.legacyChildCeilingMb)
  : null;
const modelable = rawCeilingMb !== null && rawCeilingMb >= MIN_CHILD_HEAP_MB;
const maxConcurrentChildren = modelable ? admissible : 0;
const perChildCeilingMb = modelable ? rawCeilingMb : null;

Tests: added the budgetMb axis as it.each([768, 900, 1000, 1023]) with budgetMb: 1024, each case first asserting the shape that makes it reachable (childPoolMb >= 512 and legacyChildCeilingMb < 512) so a change to either derivation retires the test loudly instead of leaving it passing vacuously. Paired with still models the partition at the first budget the floor allows (1024/1024 → 1 child at 512), which is the inclusive boundary and the guard against nulling unconditionally.

Mutation-verified both directions: dropping >= MIN_CHILD_HEAP_MB fails the 4 band cases and leaves the boundary case green; nulling unconditionally fails the boundary case.

Also propagated the changed null meaning to the two field JSDocs, the daemon status type, the SDK mirror, and the protocol doc — null no longer means only "no child is admissible".

The unmeasurable numerator (review 1 §2)

Taken. You are right that this is the one place the PR breaks its own doctrine, and that the test enshrined it.

The fix needed two helpers rather than one, because the asymmetry is real: 0 is a reading for a numerator and not for a denominator. A daemon using no memory is merely implausible; dividing by no memory is undefined. So usableDenominator keeps the > 0 coercion and usableNumerator returns number | null on >= 0, with each side measured only when both its parts are usable. A NaN RSS now falls through to source: 'heap', or to 'unknown' when neither side survives.

Strengthened the test to assert source: 'unknown' — without that assertion the case passes just as well when the numerator is coerced and divided anyway, which is exactly how it shipped. Added treats a zero numerator as a reading, not as an unusable gauge; mutating >= 0 to > 0 fails it, so both helpers are load-bearing rather than one being decoration.

rssRatio's denominator on an uncontained host (review 2 §1)

Taken as documentation, and agreed it is a denominator problem rather than a threshold one — no threshold choice fixes a denominator measuring the wrong thing. Added to the availableBytes JSDoc and to 19-observability.md: under availableMemorySource: 'host', read rssRatio as a lower bound on real pressure, with your 64 GB / 55 GB-neighbour example as the concrete shape.

refusals and channel swaps (review 1 §3, review 2 §2)

Taken as documentation, on ChildHeapPolicySnapshot.refusals, the daemon status type, the SDK mirror, and the protocol doc — including your smaller point that maxConcurrentChildren: 0 on a tiny host makes refusals equal the total spawn count, pointing at insufficientMemory as the field that explains it.

Not taking the other option you offered ("leave swap headroom in the comparison"). Headroom would admit a 26th ceiling against a 25-child pool — trading a metric artifact for real overcommit, which is the accumulation this partition exists to prevent. The counter being noisy at capacity is the cheaper problem, and it is now stated where it will be read.

Pushing back — the sampler warn amplification (review 2 §4)

The 25-warnings-a-tick figure cannot happen, and review 1 already had this right: "refreshChildResource catches internally and never rejects (it tees to teeServeDebugLine), so the .catch(err => daemonLog.warn(...)) is unreachable." That is correct at bridge.ts:3564 — the RPC failure is caught inside, the last good cache is kept, and the reason goes to the serve debug log. The handler has never fired, so the fan-out cannot multiply it. The two reviews contradict each other here and the first one is right.

Kept the handler rather than deleting it as dead code, for a reason neither review raises: refreshChildResource is an optional interface member and an async one, so any other implementation throwing before its own try block surfaces as an unhandled rejection and takes the daemon down. It stays as a documented backstop — and took your actual suggestion by giving it the workspace, since an unattributable warning repeated across a 25-workspace fan-out is precisely the shape nobody can act on.

Deferred — a unit test for the fan-out (review 2 §4, second half)

Fair push, and I am not going to claim the pre-existing-coverage argument carries it: this change is what makes children.rssBytes non-zero at all. The honest blocker is that the loop lives inline in runQwenServe's body with no seam — driving it from a test means extracting the sampler from a ~4,600-line function, which is a refactor with a larger blast radius than the thing it would cover. Tracking it as follow-up rather than pretending it is cheap.

Smaller notes

Note Status
Duplicate expect(memory?.enforced).toBe(false) Removed
perChildCeilingMb: expect.any(Number) is host-dependent Fixed — and it was worse than noted, since the fix above adds a second way a real host reaches null. Now pins the key set (so an unannounced wire field still fails) and branches on which case the host took
bridge.test.ts assigns Date.now directly Now vi.spyOn(Date, 'now') + mockRestore()
'off' | 'observe' hand-written in six places Child-heap side now imports ChildHeapMode (3 sites). import type, so it is erased before the fast-path bundle closure check sees it. Left memoryPressureMode alone deliberately: same two values today, but an independent switch, and aliasing them couples whichever gains enforce first to the other. SDK mirror stays standalone as you noted
Markdown table renders two ways Normalized both files to `off` | `observe`
Release-note line for childRssCoverage Already covered — CHANGELOG.md is generated from GitHub Releases and marked do-not-edit, so the PR body's Risk & Scope is where this lives, and it already states that clients pinning childRssCoverage === 'primary_only' will see 'active_children'. The protocol doc carries older daemons send primary_only beside it

Not taken: the workspaceRegistry-absent-with-live-workspaceSnapshots case. You are right it is the one shape where sampled: 0 is structural rather than watcher-gated, but the invariant holds and the field docs already say rssBytes: 0 with sampled: 0 never means a measured zero — so a test would pin the current wiring rather than a contract. Happy to add it if you would rather have it pinned.


Gate: tsc clean on acp-bridge, cli, and sdk-typescript; the child-heap, spawn-channel, pressure, daemon-status, serve-command, and bridge suites pass; prettier clean.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally. Not reviewed: build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux. Not reviewed: reverse audit — stopped before round 5 by the review time budget.

中文说明

已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。 未审查:build-and-test — Test (macos-latest / windows-latest, Node 22.x) legs were skipped in CI; platform-specific memory detection paths were exercised only on Linux。 未审查:反向审计——评审时间预算不足,未能开始第 5 轮。

— qwen3.8-max via Qwen Code /review (v0.21.6)

`runtime.activity` reports daemon-wide prompt activity. `activePrompts` counts sessions with an in-flight prompt. `pendingPrompts` counts all accepted prompts that have not settled yet, including the running prompt and FIFO-waiting prompts. `queuedPrompts` counts FIFO-waiting prompts that have been accepted but not dispatched. `lastActivityAt` is the ISO 8601 timestamp of the last prompt start/end or session spawn; `null` when the daemon has never processed any activity since boot. `idleSinceMs` is computed from `lastActivityAt` at response generation time.

`limits.memory` is additive and reports the daemon's resolved memory figures: a required `enforced: false`, `configuredBudgetMb`, `effectiveBudgetMb` (the configured value capped at resolved cgroup/host memory), `budgetSource` (`flag` / `derived`), `availableMemoryMb`, `availableMemorySource` (`constrained` / `host`), `insufficientMemory`, and a `modeled` object holding `rootReserveMb`, `childPoolMb`, `minChildHeapMb`, `maxChildHeapMb`, and `legacyChildCeilingMb` (a conservative model of the ceiling an ACP child receives today, which can sit below the real figure). `runtime.memory` additionally reports `registeredWorkspaces` (the registration count — non-removed workspace entries, including draining, transitioning, or blocked ones; not a live-child count), `activeAcpChildren` (daemon-managed ACP children with a live, non-dying channel — includes transitioning or blocked entries, but excludes a workspace whose kill has started even if the child has not exited; not channel workers, MCP descendants, or unattached spawn reservations), `childRssCoverage` (`primary_only` today), and a `modeled` object holding `recommendedShareAtRegisteredMb` (`null` when no workspace is registered) and `recommendedShareAtActiveMb` (`null` when no child is active). Each share is capped at the legacy child ceiling, and floored at the minimum child heap only when the ceiling allows — on a small host the ceiling sits below the floor, so share × count can exceed the child pool. Read a share as advisory, not a partition of the pool. All of it is observation: no child spawn argument derives from these values, and no request is refused on their basis. On the normal `runQwenServe` path the budget is resolved before the bootstrap app is created, so `limits.memory` is already populated during the bootstrap window. It is `null` only on paths that resolve no budget (such as direct-embed bypassing `runQwenServeImpl`). The SDK type allows `null`, so correct clients cope.
`limits.memory` is additive and reports the daemon's resolved memory figures: a required `enforced: false`, a `childHeap` object (`mode`; `maxConcurrentChildren` and `perChildCeilingMb`, both `null` under `mode: 'off'`, which models nothing — and `perChildCeilingMb` additionally `null` wherever no partition can be modeled within `modeled.minChildHeapMb` — either the pool cannot cover one child at that floor, or the ceiling would land under it once capped at `modeled.legacyChildCeilingMb`, which is `floor(available / 2)` and so drops under the floor on a host below 1024 MB. It is never 0, and `maxConcurrentChildren` is `0` in those cases, since a host that models no partition is a computed answer rather than an absent model; and `refusals`, the spawns that would have exceeded the modeled limit), `configuredBudgetMb`, `effectiveBudgetMb` (the configured value capped at resolved cgroup/host memory), `budgetSource` (`flag` / `derived`), `availableMemoryMb`, `availableMemorySource` (`constrained` / `host`), `insufficientMemory`, and a `modeled` object holding `rootReserveMb`, `childPoolMb`, `minChildHeapMb`, `maxChildHeapMb`, and `legacyChildCeilingMb` (a conservative model of the ceiling an ACP child receives today, which can sit below the real figure). `runtime.memory` additionally reports `registeredWorkspaces` (the registration count — non-removed workspace entries, including draining, transitioning, or blocked ones; not a live-child count), `activeAcpChildren` (daemon-managed ACP children with a live, non-dying channel — includes transitioning or blocked entries, but excludes a workspace whose kill has started even if the child has not exited; not channel workers, MCP descendants, or unattached spawn reservations), `childRssCoverage` (`active_children` — every ACP child with a live channel, which is the set `activeAcpChildren` counts; older daemons send `primary_only`), a `children` object described below, and a `modeled` object holding `recommendedShareAtRegisteredMb` (`null` when no workspace is registered) and `recommendedShareAtActiveMb` (`null` when no child is active). Each share is capped at the legacy child ceiling, and floored at the minimum child heap only when the ceiling allows — on a small host the ceiling sits below the floor, so share × count can exceed the child pool. Read a share as advisory, not a partition of the pool. All of it is observation: no child spawn argument derives from these values, and no request is refused on their basis. `childHeap` models a fixed partition of `modeled.childPoolMb` — every child would receive the same `perChildCeilingMb`, so the modeled total stays inside the pool rather than accumulating as a per-spawn share would. Read `refusals` as admission pressure only: a count of 0 does **not** mean the partition is safe to apply, because children run on the much larger host-derived ceiling, so a workload needing more old space than `perChildCeilingMb` is healthy here and would only fail once the partition were applied. Two further reasons a nonzero count need not mean capacity pressure: the admission decision counts a terminating child until it exits, so on a daemon already at `maxConcurrentChildren` every channel replacement books a refusal during the overlap window; and on a host too small to model a partition `maxConcurrentChildren` is `0`, so `refusals` equals the total ACP spawn count, with `insufficientMemory` as the field that explains it. On the normal `runQwenServe` path the budget is resolved before the bootstrap app is created, so `limits.memory` is already populated during the bootstrap window. It is `null` only on paths that resolve no budget (such as direct-embed bypassing `runQwenServeImpl`). The SDK type allows `null`, so correct clients cope.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-1: The modeled.legacyChildCeilingMb definition in this paragraph — "which is floor(available / 2)" — omits the 16 GB cap. The implementation is Math.min(Math.floor(memoryMb * LEGACY_CHILD_HEAP_FRACTION), MAX_CHILD_HEAP_MB) with MAX_CHILD_HEAP_MB = 16_384 (daemon-memory-budget.ts), and the sibling JSDocs define it as "min(50% of available memory, 16 GB)". — Failure scenario: probe at the reviewed commit — on a 64 GB host the wire reports legacyChildCeilingMb: 16384 while this formula yields 32768, twice the real value on every host above ~32 GB; an operator hand-checking the partition math (which these docs invite) computes a wrong expected ceiling and reads a contradiction into the triage block. Suggested fix: "which is min(floor(available / 2), 16 GB) and so drops under the floor on a host below 1024 MB".

中文说明

本段对 modeled.legacyChildCeilingMb 的定义——「即 floor(available / 2)」——遗漏了 16 GB 上限。实现为 Math.min(Math.floor(memoryMb * LEGACY_CHILD_HEAP_FRACTION), MAX_CHILD_HEAP_MB),其中 MAX_CHILD_HEAP_MB = 16_384(daemon-memory-budget.ts),同级 JSDoc 也定义为「min(可用内存的 50%, 16 GB)」。失效场景:已在被审提交上探针验证——64 GB 主机上 wire 报告 legacyChildCeilingMb: 16384,而按此公式会算出 32768,在所有 ~32 GB 以上主机上都是真实值的两倍;按文档指引手工核对分区计算的运维会得出错误的期望上限,在排查区块里读出自相矛盾。建议改为:「即 min(floor(available / 2), 16 GB),因此在低于 1024 MB 的主机上会跌到下限之下」。

— qwen3.8-max via Qwen Code /review (v0.21.6)

// undefined. Hence the two helpers rather than one.
const rss = usableNumerator(input.rssBytes);
const heapUsed = usableNumerator(input.heapUsedBytes);
const rssBytes = rss ?? 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-2: Test-efficacy probe (harness validated, mutant-survived): deleting this ?? 0 fallback left every test added or changed by this diff green. The miss path (unusable/NaN rssBytes) is exercised only through the rssMeasured guard; nothing pins the fallback's output fields. — Failure scenario: when a future edit starts emitting rssBytes on the wire or using it outside the rssMeasured guard, a null/unusable value flows through silently while the 185-line test file stays green — a regression in the miss path ships. Suggested fix: add a case in daemon-memory-pressure.test.ts feeding rssBytes: NaN and asserting the resulting rssBytes/rssRatio/source, so removing the fallback turns the suite red.

中文说明

测试有效性探针(harness 已验证、变异存活):删除这个 ?? 0 兜底后,本 diff 新增或修改的所有测试仍然全绿。未命中路径(不可用/NaN 的 rssBytes)只经由 rssMeasured 门控被覆盖,没有任何测试钉住该兜底的输出字段。失效场景:未来若有改动把 rssBytes 发到 wire 上或在 rssMeasured 门控之外使用它,null/不可用值会静默流出,而这个 185 行的测试文件依旧全绿——未命中路径上的回归会绿色发布。建议:在 daemon-memory-pressure.test.ts 中补一个输入 rssBytes: NaN 的用例,断言结果的 rssBytes/rssRatio/source,使删除该兜底时测试变红。

— qwen3.8-max via Qwen Code /review (v0.21.6)

const rss = usableNumerator(input.rssBytes);
const heapUsed = usableNumerator(input.heapUsedBytes);
const rssBytes = rss ?? 0;
const heapUsedBytes = heapUsed ?? 0;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-3: Same measured gap on the heap side (test-efficacy probe, harness validated, mutant-survived): dropping this ?? 0 fallback left every affected test green — no test exercises the unusable-heapUsedBytes miss path. — Failure scenario: a future edit using heapUsedBytes outside the heapMeasured guard (or serializing it into the status payload) propagates null into the wire contract with the whole test file staying green. Suggested fix: add a case feeding an unusable heapUsedBytes and asserting the resulting heapUsedBytes/heapRatio/source.

中文说明

堆侧的同类实测缺口(测试有效性探针,harness 已验证、变异存活):删除这个 ?? 0 兜底后所有受影响测试仍全绿——没有测试覆盖 heapUsedBytes 不可用的未命中路径。失效场景:未来若有改动在 heapMeasured 门控之外使用 heapUsedBytes(或把它序列化进 status 载荷),null 会渗入 wire 契约,而整个测试文件依旧全绿。建议:补一个输入不可用 heapUsedBytes 的用例,断言结果的 heapUsedBytes/heapRatio/source

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +30 to +32
* A side is usable only when *both* its numerator and its denominator are, so
* this is also the field that says a reported `rssBytes: 0` / `heapUsedBytes:
* 0` is a placeholder rather than a reading.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-9: This JSDoc claims source is "the field that says a reported rssBytes: 0 / heapUsedBytes: 0 is a placeholder rather than a reading", but when both sides are measured and the other side wins, source names the winner and says nothing about the loser's zero. — Failure scenario: probe at the reviewed commit — a genuine rssBytes: 0 reading beside heapRatio 0.5 produces byte-identical output (all nine fields, source: 'heap') to the rssBytes: NaN unusable case; the symmetric heap pair holds too. No wire field resolves the ambiguity, so a consumer following this rule discards a genuine zero reading — the exact confusion this module exists to prevent. The sole production caller (process.memoryUsage()) cannot emit the ambiguous input today; the defect is the contract text, which 19-observability.md repeats. Suggested fix: weaken to "unknown marks both zeros as placeholders; when one side is named, source alone cannot say whether the other side's zero is a placeholder", or drop the sentence.

中文说明

该 JSDoc 声称 source 是「能说明所报告的 rssBytes: 0 / heapUsedBytes: 0 是占位值而非真实读数的字段」,但当两侧都可测量且另一侧胜出时,source 只指出胜者,对败者的零什么也没说。失效场景:已在被审提交上探针验证——heapRatio 为 0.5 时真实的 rssBytes: 0 读数,与 rssBytes: NaN 不可用的情形产生逐字节相同的输出(全部九个字段、source: 'heap');堆侧对称情形同样成立。wire 上没有任何字段能消解这一歧义,按此规则行事的消费者会丢弃真实的零读数——正是本模块声称要防止的混淆。当前唯一的生产调用方(process.memoryUsage())不会产生这种歧义输入;缺陷在契约文本本身,且 19-observability.md 复述了同一说法。建议弱化为:「unknown 表示两个零都是占位值;当指名了某一侧时,仅凭 source 无法判断另一侧的零是否为占位值」,或直接删除该句。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +162 to +163
its numerator and its denominator were usable, so `source` is also what tells a
zero `rssBytes` / `heapUsedBytes` apart from a real one.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-9 (occurrence 2 of 2): This sentence repeats the claim that source tells a zero rssBytes / heapUsedBytes apart from a real reading, which the wire shape cannot do whenever both sides were measurable — probe at the reviewed commit produced byte-identical outputs for a genuine zero reading and an unusable side (see the sibling comment on daemon-memory-pressure.ts). — Failure scenario: an oncall following this recipe's own instruction ("check source before acting") builds a rule that discards genuine zero readings exactly in the case this sentence exists to resolve. Fixing the JSDoc leaves this sentence wrong. Suggested fix: "source names the side that produced ratio; when it is not unknown, the other side's zero cannot be told apart from a placeholder."

中文说明

R6-9(第 2 处,共 2 处):本句复述了「source 能区分零值的 rssBytes / heapUsedBytes 是占位还是真实读数」的说法,但只要两侧都可测量,wire 形状就做不到这一点——已在被审提交上探针验证:真实零读数与不可用一侧产生逐字节相同的输出(见 daemon-memory-pressure.ts 上的姊妹评论)。失效场景:按本配方自身指引(「行动前先查 source」)行事的 oncall,会构造出一条恰好在本句所要解决的场景里丢弃真实零读数的规则。只改 JSDoc 会让本句仍然错误。建议改为:「source 指明产生 ratio 的那一侧;当它不是 unknown 时,另一侧的零无法与占位值区分」。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +82 to +83
* - **Hosts too small to model a partition.** `maxConcurrentChildren` is 0
* there, so this equals the total ACP spawn count. Correct by the

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-15: The documented contract "on hosts too small to model a partition, refusals equals the total ACP spawn count" (stated here, in qwen-serve-protocol.md, and in the SDK mirror) has no test: no suite calls decide() on a policy whose maxConcurrentChildren is 0 — the 512 MB, 1024 MB-derived, and 768–1023 MB band tests assert the snapshot shape but never call decide(). The code honors the contract today. — Failure scenario: a future edit special-casing the non-modelable state — e.g. an early return mirroring the existing off one ("model nothing, refuse nothing") — ships green and silently zeroes the refusal counter on exactly the small hosts this JSDoc warns report alarming counts, removing the only admission-pressure signal those daemons emit. Suggested fix: extend the pool-0 or band test to call decide(1)/decide(n) and assert refuse: true and snapshot().refusals equal to the call count.

中文说明

文档契约「在太小而无法建模分区的主机上,refusals 等于 ACP 派生总数」(此处、qwen-serve-protocol.md 与 SDK 镜像均有表述)没有测试:没有任何套件对 maxConcurrentChildren 为 0 的策略调用过 decide()——512 MB、1024 MB 推导、以及 768–1023 MB 区间测试都只断言快照形状,从不调用 decide()。当前代码是履行该契约的。失效场景:未来若有改动为不可建模状态加特判——例如仿照现有 off 早退再加一个早退(「不建模就不拒绝」)——会绿色发布,恰好在这份 JSDoc 警告会报出惊人读数的小主机上把拒绝计数悄悄清零,抹掉这些 daemon 唯一的准入压力信号。建议:在 pool-0 或区间测试中调用 decide(1)/decide(n),断言 refuse: truesnapshot().refusals 等于调用次数。

— qwen3.8-max via Qwen Code /review (v0.21.6)

Comment on lines +612 to +613
childRssBytesTotal += snapshot.rssBytes;
childRssSampled += 1;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] R6-16: The new cross-child sum accumulates snapshot.rssBytes with no non-negativity guard, and the bridge trust boundary does not stop a negative: refreshChildResource checks Number.isFinite but not sign — it clamps cpuPercent to [0,100] on receive under a comment naming "a misbehaving child", while rssBytes gets no sign bound. — Failure scenario: probe at the reviewed commit — a child answering workspaceResource with rssBytes: -123456, cpuPercent: -5 caches -123456 (the sibling clamps to 0 in the same payload), so one misbehaving child subtracts from every other child's reading and can drive runtime.memory.children.rssBytes negative, contradicting its own "a floor rather than a total" JSDoc, while still counting in sampled. Hardening gap, not a live bug: production-spawned children are honest. Suggested fix (probe-verified): clamp at the boundary in bridge.ts, symmetric with cpuPercentinfo.childRssBytes = Math.max(0, res.rssBytes).

中文说明

新的跨子进程求和直接累加 snapshot.rssBytes,没有非负防护,而 bridge 的信任边界也拦不住负数:refreshChildResource 检查 Number.isFinite 却不检查符号——它在接收时把 cpuPercent 钳到 [0,100],注释点名威胁是「行为不端的子进程」,rssBytes 却没有符号约束。失效场景:已在被审提交上探针验证——子进程对 workspaceResource 应答 rssBytes: -123456, cpuPercent: -5 时,-123456 被缓存(同一载荷里姊妹字段被钳为 0),于是一个行为不端的子进程会从其他所有子进程的读数里扣减,可以把 runtime.memory.children.rssBytes 打成负数,与其自身「是下限而非总和」的 JSDoc 矛盾,同时仍计入 sampled。这是加固缺口而非现行 bug:生产派生的子进程是诚实的。建议(已探针验证):在 bridge.ts 的边界处对称钳制——info.childRssBytes = Math.max(0, res.rssBytes)

— qwen3.8-max via Qwen Code /review (v0.21.6)

@wenshao

wenshao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Local verification report — real daemons, not mocks

I built both sides and ran the whole Reviewer Test Plan against live qwen serve daemons on Linux, plus a set of checks the plan does not cover. All seven steps pass. One item in the test plan is not reproducible as written — the instruction is wrong, the code is not, and the shipped docs are correct — plus two small notes. Detail below.

Environment

Host Linux 6.12, 16 cores, os.totalmem() 32 163 381 248 B → 30 673 MB, cgroup v2
BASE arm worktree at merge-base 2c514b50b9, full npm run build
PR arm worktree at PR head a909ff5a14, full npm run build
Daemons 11 real qwen serve processes (10 at PR head, 1 at merge-base): 4 unconstrained, 7 inside systemd-run --scope -p MemoryMax=… cgroups (512 M / 768 M / 1024 M / 240 M / 300 M)
Workspaces up to 3 registered, real ACP children, real SSE stream

A detail worth recording, because it makes this host a good test bed: process.constrainedMemory() here returns the v1 "unlimited" sentinel 18446744073709552000. detectAvailableMemoryMb() rejects it and falls back to host total (availableMemorySource: "host", 30 673 MB), while getAcpMemoryArgs() does not and lands on the 16 GB cap. That is exactly the second divergence documented on legacyChildCeilingMb — observed live, not inferred: legacyChildCeilingMb: 15336 beside a child actually running --max-old-space-size=16384.

Test plan — results

# Check Result
1 runtime.memory.pressure, source names the winning denominator source: "heap" unconstrained (0.0192 heap vs 0.0053 rss); flips to "rss" under cgroup (0.659 vs 0.309). Worse-one-wins confirmed in both directions.
2 off reports every figure, drops only the verdict ✅ Sharpest possible arm — see below
3 childRssCoverageactive_children, sampled <= activeAcpChildren true throughout
4 sampled: 0 is never a measured zero ✅ behaviour — ⚠️ the stated repro cannot produce it (F1)
5 max × ceiling <= childPoolMb ✅ on four host sizes
6 Nothing applied; enforce rejected ✅ child argv byte-identical, 16384perChildCeilingMb 572
7 Unit suites 944 pass (409 cli + 535 acp-bridge), 0 fail

prettier --check, eslint, and tsc --noEmit are clean on every changed file across cli, acp-bridge, and sdk-typescript.

Step 1 & 5 — before / after on one host

limits.memory.childHeap on this 30 673 MB host models 25 children at 572 MB (25 × 572 = 14 300 ≤ pool 14 312). That is the same formula the PR pins at 25 × 614 for a clean 32 768 MB host; the difference is only that this box reports 30 673 MB.

before and after

Step 2 — off keeps the reading and drops the verdict

This is the assertion most likely to pass vacuously, because on a healthy daemon level is normal and the gate could be deleted without breaking anything. So I put two daemons in identical 240 MB cgroups and changed nothing but --memory-pressure-mode. Both reach level: "hard" from real RSS pressure:

  • observestatus: "warning", one daemon_memory_pressure issue at severity warning (not error)
  • offstatus: "ok", issues: [], level: "hard" still fully reported

The pressure block is key-for-key identical in both modes.

off vs observe

Steps 3 & 4 — the child aggregate, and where the test plan is wrong

The lifecycle behaves exactly as documented: sampled: 0 on an idle daemon, sampled: 2 once a stream is open, readings persisting after the watcher closes until oldestReadingAgeMs crosses ~30 000 ms, then dropping to sampled: 0 / rssBytes: 0 / oldestReadingAgeMs: null while activeAcpChildren stays 2.

children lifecycle

F1 — test-plan step 4 cannot produce what it asks for. It says: two workspaces open, only one streaming → sampled: 1 against activeAcpChildren: 2. I get sampled: 2, and that is correct behaviour. The sampler gate is daemon-global — getActiveSseCount() is a module-level counter in routes/sse-events.ts, and run-qwen-serve.ts fans the refresh out over workspaceRegistry.listManaged() in full — so one stream anywhere refreshes every live child. The code is better than the instruction describes; only the instruction is wrong.

The shortfall the field exists for is real, and I did reproduce it: register a third workspace and open a session while the sampler is running, and for one 5 s tick the status reads sampled: 2 against activeAcpChildren: 3 — the new child has no cached reading yet, the sum is a floor, and it says so. That is the repro worth putting in the PR body.

Worth stressing for anyone else reading: this is confined to the PR description. docs/developers/daemon/19-observability.md, docs/developers/qwen-serve-protocol.md, and docs/users/qwen-serve.md all describe the gate correctly. Nothing ships wrong.

Step 6 — nothing is applied

nothing applied

Both --child-heap-mode values produce byte-identical child argv at the host-derived 16384, nowhere near the modeled 572. Which also means #8182 is visibly still open on this box: 25 authorised children × 16 384 MB = 400 GB against a 14 312 MB pool, exactly as the PR says it leaves it.

Both --child-heap-mode enforce and --memory-pressure-mode enforce are rejected by yargs. I also drove parseServeFastPathArgs directly: valid values are carried through on the fast path, enforce and any other bad value return fallback so yargs owns the error message.

Part 3 — the two review-driven fixes, on real hosts

Both defects the review drove out are reachable through real cgroups, not just unit fixtures:

host matrix

  • 512 MB, derived budget — root reserve 256 consumes the entire 256 MB budget, pool 0 → maxConcurrentChildren: 0, perChildCeilingMb: null, insufficientMemory: true. The pre-fix behaviour here modeled a ceiling of 0, i.e. V8's ~4 GB default.
  • 768 MB + --memory-budget-mb 1024 — the sub-floor band, reached through exactly the flag docs/users/qwen-serve.md:398 recommends on small hosts. Pool 512 / 1 child = 512, capped at legacyChildCeilingMb 384, which is under minChildHeapMb 512 → refused rather than shrunk. max: 0, ceil: null.
  • 1024 MB + --memory-budget-mb 1024 — the inclusive boundary: one child at exactly 512.

Beyond the test plan

Mutation sweep. I reverted each guard in source and re-ran the suites, to confirm the tests pin behaviour rather than merely covering lines:

Mutation Tests that fail
drop the MB→bytes conversion on availableBytes 3
Math.maxMath.min on the pressure ratio 12
delete the mode === 'observe' gate on the issue 1
sampled := activeAcpChildren 3
drop the isChannelLive() gate from the sum 1
widen the staleness cliff to 10× the window 1 (bridge)
move decide() back outside the try 1 — "releases the reservation when a supplied policy throws"
revert the sub-floor guard 4
revert the zero-pool clamp alone 0
revert both Part-3 guards together 6

Every guard the PR claims is mutation-verified is caught. Counts differ from the PR body in places only because I ran different suite subsets.

The one entry worth a comment is the second-to-last: dropping Math.max(1, …) from admissible on its own fails nothing, because the rawCeilingMb >= MIN_CHILD_HEAP_MB guard subsumes it (floor(0/1) = 0 < 512). Not a defect — the zero-pool regression can only return if both guards go — but the two fixes are pinned by one predicate rather than two, which is worth knowing before either is refactored.

Bootstrap window. The caveat on the pressure type comment is real and I caught it live: polling /daemon/status from process start, there is a window where limits.memory is fully populated with enforced: false while runtime.memory is absent entirely and limits.memory.childHeap is null. Clients must not assume "budget resolved ⇒ pressure present" — the comment says so, and it is accurate.

Metrics ring unchanged. With two children sampled, the ring's childRssBytes read 185 774 080 against a children.rssBytes aggregate of 363 151 360 — the primary child alone. The published singular meaning is intact.

Two notes, neither blocking

F2 — refusals is the one childHeap field that does not distinguish "not modeled" from "zero". On the same 512 MB host: --child-heap-mode observerefusals: 1; --child-heap-mode offrefusals: 0. Under off, decide() returns early and never counts, so 0 means "not evaluated", while an operator reads it as "no spawn would have been refused". The PR argues carefully for null over 0 on maxConcurrentChildren and perChildCeilingMb for precisely this reason; refusals is the field that did not get the same treatment. A one-line change (refusals: modeled ? refusals : null) plus the SDK mirror would make the block internally consistent. Happy for this to be follow-up.

F3 — calibration data: critical is effectively unreachable on a cgroup-bound daemon. Idle root RSS sits at 165–175 MB, so rssRatio >= 0.8 needs MemoryMax <= ~215 MB — and at 220 MB and below the kernel OOM-kills the daemon before /daemon/status can report anything. I reached soft (300 M) and hard (240 M); every attempt at critical on the RSS side died. In the container case the usable band is soft/hard, and critical will show up mostly on the heap side or not at all. That is a point for the PR's own "uncalibrated for a long-running daemon" caveat, and worth recording now for whoever does the calibration.

Not covered

macOS and Windows, left to CI as the PR states. source: "unknown" is unreachable from the production caller (process.memoryUsage() cannot return NaN) and stays covered by unit tests only. The channel-swap refusal artifact needs 25 concurrent children and was not exercised.

Verdict

The behaviour on the wire matches what the PR describes, on real daemons, including both edge cases the earlier review rounds forced out. Merge-ready from my side. The only thing I would fix before merge is the step-4 instruction in the PR body, since it will send the next reviewer looking for a bug that is not there — and it is a description change, not a code change.

中文版

本地验证报告 —— 真实 daemon,非 mock

我构建了双侧代码,针对真实运行的 qwen serve daemon(Linux)跑完了整份 Reviewer Test Plan,另加若干测试计划未覆盖的检查。七个步骤全部通过。 测试计划中有一项按其字面描述无法复现——说明写错了,代码没错,随 PR 发布的文档也是对的——另有两点提醒。详见下文。

环境

主机 Linux 6.12,16 核,os.totalmem() 32 163 381 248 B → 30 673 MB,cgroup v2
BASE 侧 merge-base 2c514b50b9 的 worktree,完整 npm run build
PR 侧 PR head a909ff5a14 的 worktree,完整 npm run build
Daemon 11 个真实 qwen serve 进程(10 个在 PR head,1 个在 merge-base):4 个无约束,7 个跑在 systemd-run --scope -p MemoryMax=… cgroup 内(512 M / 768 M / 1024 M / 240 M / 300 M)
Workspace 最多注册 3 个,真实 ACP 子进程,真实 SSE 流

有一个细节值得记录,正是它让这台机器成为合适的试验台:这里 process.constrainedMemory() 返回 v1 的「无限」哨兵值 18446744073709552000detectAvailableMemoryMb() 拒绝了它并回退到主机总量(availableMemorySource: "host",30 673 MB),而 getAcpMemoryArgs() 没有拒绝,于是落到 16 GB 上限。这正是 legacyChildCeilingMb 上记录的第二处背离——是实测而非推断:legacyChildCeilingMb: 15336,而旁边的子进程实际跑着 --max-old-space-size=16384

测试计划 —— 结果

# 检查项 结果
1 runtime.memory.pressuresource 指明胜出的分母 ✅ 无约束时 source: "heap"(heap 0.0192 vs rss 0.0053);进 cgroup 后翻转为 "rss"(0.659 vs 0.309)。两个方向都验证了「取更差者」
2 off 报告全部数值,只去掉判定 ✅ 用了最锋利的一组对照,见下
3 childRssCoverageactive_childrensampled <= activeAcpChildren ✅ 全程 true
4 sampled: 0 绝非测得为零 ✅ 行为正确 —— ⚠️ 但所给的复现步骤产生不出它(F1)
5 max × ceiling <= childPoolMb ✅ 四种主机规格均成立
6 什么都没被应用;enforce 被拒绝 ✅ 子进程 argv 逐字节一致,16384perChildCeilingMb 572
7 单元测试套件 944 通过(cli 409 + acp-bridge 535),0 失败

cliacp-bridgesdk-typescript 三者所有改动文件上的 prettier --checkeslinttsc --noEmit 全部干净。

步骤 1 与 5 —— 同一主机上的前后对比

本机 30 673 MB 上 limits.memory.childHeap 建模为 25 个子进程、每个 572 MB(25 × 572 = 14 300 ≤ 池 14 312)。这与 PR 在干净 32 768 MB 主机上钉住的 25 × 614 是同一个公式,差别只在于本机报告的是 30 673 MB。

before and after

步骤 2 —— off 保留读数,只丢掉判定

这是最容易「空过」的断言:健康 daemon 上 level 恒为 normal,即便删掉门控断言也照样成立。所以我把两个 daemon 放进完全相同的 240 MB cgroup,只改 --memory-pressure-mode。两者都因真实 RSS 压力达到 level: "hard"

  • observestatus: "warning",一条 daemon_memory_pressure,severity 为 warning(不是 error
  • offstatus: "ok"issues: [],但 level: "hard" 依然完整报出

pressure 块在两种模式下键对键完全一致。

off vs observe

步骤 3 与 4 —— 子进程汇总,以及测试计划的错处

生命周期表现与文档完全一致:空闲 daemon 上 sampled: 0;开流后 sampled: 2;观察者关闭后读数继续留存,直到 oldestReadingAgeMs 越过 ~30 000 ms,随即跌为 sampled: 0 / rssBytes: 0 / oldestReadingAgeMs: null,而 activeAcpChildren 保持 2。

children lifecycle

F1 —— 测试计划第 4 步产生不出它所要求的结果。 它写的是:打开两个 workspace、只有一个在流式传输 → sampled: 1activeAcpChildren: 2。我得到的是 sampled: 2,而这才是正确行为。采样门控是 daemon 全局的——getActiveSseCount()routes/sse-events.ts 里的模块级计数器,而 run-qwen-serve.ts 会对 workspaceRegistry.listManaged() 全量做 fan-out——所以任意一处的流都会刷新每一个存活子进程。代码比说明描述得更好,错的只是说明。

该字段真正要覆盖的缺口是存在的,我也复现了:在采样器运行期间注册第三个 workspace 并开一个 session,接下来的一个 5 秒 tick 内状态读作 sampled: 2activeAcpChildren: 3——新子进程还没有缓存读数,求和是个下界,而它如实说明了这一点。这才是应该写进 PR 描述的复现步骤。

需要强调,供其他阅读者参考:这一问题只存在于 PR 描述中。 docs/developers/daemon/19-observability.mddocs/developers/qwen-serve-protocol.mddocs/users/qwen-serve.md 对该门控的描述都是正确的,没有任何错误内容随代码发布。

步骤 6 —— 什么都没被应用

nothing applied

两种 --child-heap-mode 取值下子进程 argv 逐字节一致,都是主机推导的 16384,与建模的 572 相去甚远。这也意味着 #8182 在本机依然明晃晃地敞开着:25 个被授权的子进程 × 16 384 MB = 对 14 312 MB 的池授权了 400 GB,正如 PR 所述——它并未关闭这个问题。

--child-heap-mode enforce--memory-pressure-mode enforce 都被 yargs 拒绝。我还直接驱动了 parseServeFastPathArgs:合法取值会被 fast path 正确带出,enforce 及任何非法值返回 fallback,把报错交给 yargs。

Part 3 —— 两处评审驱动的修复,在真实主机上

评审逼出来的两个缺陷都能通过真实 cgroup 触达,而不只是单元测试的 fixture:

host matrix

  • 512 MB,推导预算 —— root 预留 256 吃掉了全部 256 MB 预算,池为 0 → maxConcurrentChildren: 0perChildCeilingMb: nullinsufficientMemory: true。修复前这里会建模出上限 0,即 V8 约 4 GB 的默认堆。
  • 768 MB + --memory-budget-mb 1024 —— 低于下限的那一段,正是通过 docs/users/qwen-serve.md:398 建议小主机使用的那个标志触达的。池 512 / 1 个子进程 = 512,被 legacyChildCeilingMb 384 压低,而 384 低于 minChildHeapMb 512 → 拒绝建模而非压到下限之下。max: 0ceil: null
  • 1024 MB + --memory-budget-mb 1024 —— 闭区间边界:一个子进程,恰好 512。

测试计划之外

变异扫描。 我逐一在源码中回退各处保护,再跑相应套件,以确认测试是钉住了行为而非仅仅覆盖了代码行:

变异 失败测试数
去掉 availableBytes 的 MB→字节 换算 3
压力比值 Math.maxMath.min 12
删掉 issue 上的 mode === 'observe' 门控 1
sampled := activeAcpChildren 3
从求和中去掉 isChannelLive() 门控 1
把陈旧窗口放宽到 10 倍 1(bridge)
decide() 移回 try 之外 1 —— "releases the reservation when a supplied policy throws"
回退低于下限的那道保护 4
单独回退零池钳位 0
同时回退 Part 3 的两道保护 6

PR 声称经变异验证的每一处保护都被抓住了。个别数字与 PR 描述不同,仅仅因为我跑的套件子集不同。

值得一提的是倒数第二项:单独去掉 admissible 上的 Math.max(1, …) 一个测试都不会挂,因为 rawCeilingMb >= MIN_CHILD_HEAP_MB 这道保护把它吸收了(floor(0/1) = 0 < 512)。这不是缺陷——零池回归只有在两道保护都被移除时才会回来——但这两处修复实际由一个谓词钉住,而非两个;在重构任一处之前值得知道这一点。

Bootstrap 窗口。 pressure 类型注释上的那条警告是真实的,我实测捕捉到了:从进程启动就开始轮询 /daemon/status,存在一个窗口,其中 limits.memory 已完整填充且 enforced: false,而 runtime.memory 整块缺席、limits.memory.childHeapnull。客户端不能假设「预算已解析 ⇒ pressure 存在」——注释正是这么说的,且准确无误。

metrics ring 未变。 两个子进程被采样时,ring 的 childRssBytes 读作 185 774 080,而同期 children.rssBytes 汇总为 363 151 360——即前者仅含主 workspace 子进程。其已发布的单数含义完好无损。

两点提醒,均不阻塞合并

F2 —— refusalschildHeap 中唯一没有区分「未建模」与「零」的字段。 同一台 512 MB 主机上:--child-heap-mode observerefusals: 1--child-heap-mode offrefusals: 0。在 offdecide() 提前返回、从不计数,所以这个 0 的含义是「未评估」,而运维会把它读成「没有派生会被拒绝」。PR 恰恰是为了这个理由,才为 maxConcurrentChildrenperChildCeilingMb 论证了用 null 而非 0refusals 是没有获得同等待遇的那一个。一行改动(refusals: modeled ? refusals : null)加上 SDK 镜像即可让整块内部自洽。作为 follow-up 处理我也没意见。

F3 —— 校准数据:受 cgroup 约束的 daemon 上 critical 基本不可达。 空闲时根进程 RSS 稳定在 165–175 MB,因此 rssRatio >= 0.8 需要 MemoryMax <= ~215 MB——而在 220 MB 及以下,内核会在 /daemon/status 能报出任何东西之前就把 daemon OOM 掉。我达到了 soft(300 M)与 hard(240 M);RSS 侧所有冲击 critical 的尝试全部进程死亡。在容器场景下真正可用的区间是 soft/hardcritical 多半只会出现在 heap 侧,或者根本见不到。这一点是支持 PR 自己那条「未针对长运行 daemon 校准」警告的,也值得现在就记录下来,留给做校准的人。

未覆盖

macOS 与 Windows,按 PR 所述交由 CI。source: "unknown" 从生产调用方不可达(process.memoryUsage() 不会返回 NaN),仍只由单元测试覆盖。channel 交换导致的 refusal 假象需要 25 个并发子进程,未予验证。

结论

线上载荷的行为与 PR 的描述一致,在真实 daemon 上得到验证,包括前几轮评审逼出来的两个边界情形。从我这边看可以合并。 合并前唯一建议修正的是 PR 描述中第 4 步的说明,因为它会让下一位评审者去找一个并不存在的 bug——而这是描述的改动,不是代码的改动。

@wenshao

wenshao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 134 passed · 0 failed · 134 total

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:134 通过 · 0 失败 · 134 总计

Verification report

PR 8423 Deep Verification (follow-up round 2) — feat(serve): observe daemon and child memory against real denominators

Verdict: merge-ready — 134 scripted assertions executed, 134 passed, 0 failed; targeted gates 1090/1090 (acp-bridge full) + 420/420 (cli affected suites); 12/12 mutants killed on named assertions with a landed positive control.
Verified head: a909ff5a1414f128af7dc732efa9562aae85299d (git rev-parse HEAD^2; merge commit 3ac2ca9da8, base tip d5e47709a3).

中文 — 判定:✅ 通过 · 可合入(agent 判定 · 跟进轮 2)
  • 结论:merge-ready。本轮为跟进轮:分支再次更新——上轮 head e8aafa66a2 之后新增 5 个提交(两次 main 合入、实质性修复 6872d59023「低于下限的堆上限」、两次分支合并),且 base 推进到 d5e47709a3;旧 head 在 depth-2 克隆中不可达,因此全部测量在新 head a909ff5a14 重新执行,未沿用上轮数值。134 条脚本化断言全过(wire A/B 54、session 探针 18+15、模块矩阵 29、变异矩阵 18);定向门禁 acp-bridge 1090/1090、cli 受影响套件 420/420 全绿。
  • A/B 结论(见 "Central claim + A/B" 表与 01-wire-ab-head-vs-base.png):head 在 /daemon/status 给出 pressure(十字段、双分母取劣、source 指名胜者、MB→字节可在 wire 上精确对账 253266 MB = 265568649216 B)、children(空闲 {0,0,null};SSE 观察下 sampled:1、rssBytes ≈ 213 MB)、childHeap(本机 25×5024 ≤ 池 125609);base 构建(d5e47709a3 源码、base 提交戳)没有这三个字段且 childRssCoverage 仍为 primary_only,翻转干净(9/9)。
  • 本轮增量 6872d59023(低于下限的上限):模块级 band cell 证明 768/900/1000/1023 MB 宿主 + 显式 --memory-budget-mb 1024 时池过下限(512–767 MB)而 legacy 上限不过(384–511 MB),模型拒绝发布分区({0, null}),闭区间边界 1024/1024 → 1×512 与 1025/1024 → 1×512 均通过;变异矩阵中移除该守卫(mut10)红 4 个具名测试(断言 expected 384 to be null),边界改斥(mut11)红 1 个,无条件置 null(mut3)红 14 个——修复承重且测试非空转。
  • Findings:无阻塞项。五条描述性更正:C1/C2/C3 沿用上轮并复测成立(C1 差值扩大:正文称 "fails 7",本轮实测 12);C4/C5 为本轮新测得的正文计数过期(sampled := activeAcpChildren 实测 3 非 2;删 flag 实测 4 非 2)。另有一条非阻塞覆盖观察:阈值常量自身对测试套件自中和(见 Findings)。
  • 未覆盖:Windows/macOS、真实准入压力下的 refusals、WS 观察路径、双 workspace 缺口活体 cell、逐提交归因(depth-2,元数据 11 提交本地仅 HEAD^2 可达)、SDK 类型镜像独立编译门禁、文档全文审计、全仓门禁。

Previous-round findings status

This is a follow-up round. The previous round verified head e8aafa66a2 at base a5c637b749 (122/122, merge-ready). Since then the branch gained five commits — two merges of main, the substantive fix 6872d59023 ("never model a child heap ceiling below the documented minimum"), and two branch merges — and the base advanced to d5e47709a3. e8aafa66a2 is unreachable in this depth-2 clone, so nothing was carried forward by closure; every measurement below was rebuilt and re-run at the new head.

# previous item severity status at a909ff5a14
1 verdict merge-ready, 122/122 verdict re-measured: 134/134 at the new head; stands
2 C1: body "Math.max→Math.min fails 7", measured 9 correction stands, gap widened — re-measured 12 at this head (10 in daemon-memory-pressure.test.ts + 2 in daemon-status.test.ts); body still says "fails 7"
3 C2: body "MB→bytes fails 2", measured 3 correction stands — re-measured 3, incl. converts the budget from megabytes when computing the ratio (expected 1 to be 1048576); body still says "fails 2 tests"
4 bootstrap window (limits.memory populated, childHeap: null, runtime.memory absent) verified-along-the-way re-verified (wire-ab head-default cell, 3 assertions on the first fetch after listening)
5 source: heap on a large host is worse-of-two working verified-along-the-way re-verified live: heapRatio 0.02113 vs rssRatio 0.00078, source: "heap"
6 not-covered list scope unchanged; re-listed under Not covered
7 branch force-update context context recurred: head moved e8aafa66a2 → a909ff5a14; treated as untrusted rebase again; full re-measurement

No previous finding was declined; there was nothing to decline (zero blocking findings in either prior round).

Central claim + A/B

Central claim: /daemon/status reports memory observation against real denominators — root pressure (worse of RSS-vs-available and heap-vs-V8-limit), aggregate child RSS with an honest sampled count, and a modeled per-child heap partition that fits the pool — while applying nothing; off modes keep every figure but drop the verdict/partition; and (this round's delta) the modeled ceiling never lands below the documented 512 MB floor, even through the explicit-budget path.

Real daemons booted from the head build and from a base build compiled at HEAD^1 (d5e47709a3): worktree + symlink-farm node_modules with the three changed workspaces re-pointed into the base tree — readlink -f asserted: acp-bridge/qwen-code/sdk → base tree, qwen-code-core (untouched by the diff) → head tree; nested per-package node_modules (third-party only, no @qwen-code entries) shared from the head install; package.json/package-lock.json untouched by the PR, so the shared root store is a clean control. Base dist identity proven: build stamp GIT_COMMIT_INFO = 'd5e47709a3' (head: '3ac2ca9da8'), zero occurrences of active_children/daemon_memory_pressure in base daemon-status.js, and no child-heap-policy.js in base acp-bridge dist. Identical scenario per cell: boot serve --port 0 --token … --workspace …, poll until runtime.memory exists, GET /daemon/status with bearer auth. Witness: 01-wire-ab-head-vs-base.png.

cell environment oracle result
head-default head build, default flags 24 assertions: bootstrap window (limits.memory populated, childHeap: null, runtime.memory absent on the first fetch); pressure present with exactly the 10 published keys; mode: observe; ratio == max(rssRatio, heapRatio); source = winner (live: heap 0.02113 > rss 0.00078); availableBytes == availableMemoryMb × 2²⁰ exact on the wire (253266 MB → 265568649216 B); level classifies ratio; live numerators > 0; childRssCoverage: active_children; idle children {0, 0, null}; sampled ≤ activeAcpChildren; childHeap {observe, 25, 5024, 0} recomputed from the wire's own modeled figures (pool 125609, floor 512, legacy 16384); 25×5024=125600 ≤ 125609; refusals: 0; enforced === false; pressure issue only when not normal (always warning); rollup ok 24/24
base-default base build, default flags 9 assertions: boot + runtime ready; no pressure, no children; childRssCoverage: primary_only; no childHeap key; enforced: false; head runtime.memory keys == base + exactly {children, pressure}; head limits.memory keys == base + exactly {childHeap} 9/9
head-both-off head build, both flags off 17 assertions: pressure fully populated (same 10 keys, mode: off); ratio/source/level consistent; MB→bytes exact; no daemon_memory_pressure issue regardless of level; rollup ok unchanged; childHeap object present {mode: off, maxConcurrentChildren: null, perChildCeilingMb: null, refusals: 0} (null ≠ zero-pool 0); enforced: false; sampling coverage unaffected 17/17
head-enforce-×2 head build, --child-heap-mode enforce / --memory-pressure-mode enforce 2×2 assertions: daemon refuses to boot (non-zero exit, no listening line); stderr carries yargs Invalid values … Choices: "off", "observe" naming the flag 4/4

Session-level cells (witnesses: 02-session-probe-observe.png, 03-session-probe-off.png): real POST /session spawns a real qwen --acp child against a loopback fake-OpenAI decoy (0 requests received in both modes — the probe never prompts); GET /session/:id/events held open as the SSE watch gate:

probe argv oracle sampling oracle result
observe /proc-scanned child parented by the daemon carries --max-old-space-size=16384 (host-derived: min(floor(253266/2), 16384)), not perChildCeilingMb 5024, and larger than it with watcher live: children {rssBytes: 223547392, sampled: 1, oldestReadingAgeMs: 48}, activeAcpChildren: 1, partition recomputes {observe, 25, 5024, 0}, refusals: 0 after the spawn, enforced: false 18/18
off argv byte-identical: --max-old-space-size=16384 sampling still live {rssBytes: 228569088, sampled: 1, oldestReadingAgeMs: 50}; partition {off, null, null, 0} while the child is live 15/15

The delta commit 6872d59023 — sub-floor ceiling

The defect: perChildCeilingMb was min(floor(pool / admissible), legacyChildCeilingMb); the legacy term is floor(available / 2) and can sit below MIN_CHILD_HEAP_MB (512). Unreachable from a derived budget (the pool hits 0 first), but reachable through --memory-budget-mb, which docs/users/qwen-serve.md recommends on exactly those hosts. Module cells on the compiled dist (witness: 04-module-matrix.png, 29/29 overall):

cell expectation result
avail ∈ {768, 900, 1000, 1023} + explicit budgetMb: 1024 band reachable (pool 512/644/744/767 ≥ 512; cap 384/450/500/511 < 512) and model refused: {maxConcurrentChildren: 0, perChildCeilingMb: null} with minChildHeapMb: 512 beside it 4/4
boundary 1024/1024 inclusive: exactly {1, 512} 1/1
just above 1025/1024 still models {1, 512} 1/1
derived small hosts 512 / 1024 MB {0, null} (pool 0 / pool 256 < floor) — never a 0 ceiling 2/2
pins 8 GB → 7×526, 32 GB → 25×614; 1 TB ceiling capped at legacy 16384; invariant sweep over 10 host sizes; decide() refusal counting; off cells as pinned by the PR all pass

Mutation A/B on the delta: reverting the guard (mut10) publishes the sub-floor partition and is killed by the four band tests (expected 384 to be null); making the boundary exclusive (mut11) is killed by the boundary test; nulling unconditionally (mut3) is killed by 14 tests including the boundary and its paired observe control — so neither over- nor under-refusal escapes the suite.

Corrections

Description-level only — the code is correct; these numbers in the PR body are stale against the merged head (test growth arrived via the two main merges). None is a request to change code.

  1. (carried, stands, gap widened) Body: "Math.maxMath.min on the pressure ratio fails 7" — measured 12 at this head (mut4 decomposition: 10 in daemon-memory-pressure.test.ts [6 threshold-edge cases, reports the worse of the two denominators…, 3 sanitization cases] + 2 in daemon-status.test.ts [reports pressure figures in both modes…, raises nothing on a healthy daemon…]). Previous round measured 9 at the old head; the pressure suite grew in the merges.
  2. (carried, stands) Body: "dropping the MB→bytes conversion fails 2 tests" — measured 3 (mut5), the named one being converts the budget from megabytes when computing the ratio with expected 1 to be 1048576 (the 2²⁰ factor).
  3. (carried, stands, nit) The bootstrap comment in run-qwen-serve.ts still says childHeap is null "even when the flag says enforce" — enforce is not an accepted value (yargs rejection re-verified in both enforce cells). The guarded invariant itself is correct and wire-verified.
  4. (new) Body: "sampled := activeAcpChildren fails 2" — measured 3 (mut6): reports live child counts and advisory shares under runtime, sums only the children that actually reported, and says how many did, and counts a draining workspace that still holds a live child.
  5. (new) Body: "deleting the flag from the handler fails 2" — measured 4 (mut9): the two pass-through tests plus defaults the child heap mode to observe, and rejects enforce outright and defaults the memory pressure mode to observe.

Confirmations (body claims re-measured and found exact at this head): "dropping the liveness gate fails 1" (mut7: 1), "widening the staleness cliff fails the bridge test" (mut8: 1), the 8 GB → 7×526 and 32 GB → 25×614 pins.

Findings

None blocking. Every executed assertion passed; no behavioral mismatch was produced by any cell, probe, or mutant.

Coverage observation (suggestion, not a merge condition): the pressure suite's threshold fixtures are parameterized from the constants under test ([SOFT_PRESSURE_RATIO * AVAILABLE - 1, 'normal'], …), which makes constant-value mutations self-neutralizing — the round's first positive control (0.5 → 0.4) survived because the fixtures moved with the mutant. What the suite pins is classify()'s inclusive comparisons (mut0b, >= → >, killed on the exact-boundary fixture). The literal threshold values (0.5/0.65/0.8) are inherited from core's MemoryPressureMonitor and documented as that contract; if pinning the values themselves matters, a fixture with literal ratios would do it. The harness's positive control was switched to the pinned clause accordingly.

Not covered

  • Windows / macOS — ran on the lane's node:22-bookworm container. The PR's platform-sensitive decision (child self-reported RSS instead of /proc) rides on the repo's CI matrix.
  • refusals under real admission pressure — >25 concurrent sessions not driven live; covered by module-matrix decide() cells and the unit suites.
  • WS watch path — the sampler gate is sseCount > 0 || wsStreams > 0; only the SSE half was driven live (both probes).
  • Two-workspace sampled-shortfall live cell (Reviewer Test Plan step 4) — the one-workspace live case (sampled: 1 of activeAcpChildren: 1) and the idle case were driven live; the two-workspaces-one-streaming variant is pinned only by unit tests (mut6/mut7 red tests).
  • oldestReadingAgeMs with pre-field contributors — unit-covered; a genuinely older bridge was not constructed live.
  • Per-commit attribution — depth-2 checkout: the metadata lists 11 commits but git rev-list HEAD^1..HEAD^2 returns only the head merge commit, so the individual commits (incl. the merged-in main content) are unreachable and the aggregate HEAD^1..HEAD diff is what was verified. The two base-tip merges since the last round brought test growth (visible in the corrected counts) but no production change to the PR's surface beyond 6872d59023, which was verified directly.
  • SDK type mirror — read field-by-field against the observed wire (optional pressure/children/childHeap, union childRssCoverage, nullable partition fields — all consistent) and compiled by the workflow's full build; no separate SDK compile gate or mixed-version daemon/client pairing.
  • Docs — six documentation files changed; content not audited beyond the childRssCoverage/childHeap/pressure claims the wire oracle settled. The docs/users/qwen-serve.md recommendation that makes the sub-floor band reachable was verified to exist, not copy-edited.
  • Repo-wide suite / lint / typecheck — targeted gates only (acp-bridge full: 1090/1090; cli affected serve suites: 420/420). The workflow's pre-run npm run build at head is the compile evidence.
  • Base-arm purity — unchanged workspaces resolve into the head tree (realpath-asserted; the PR diff does not touch them); nested per-package node_modules shared from the head install contain only third-party packages (no @qwen-code entries, verified); the git-commit stamp is regenerated in the base tree (d5e47709a3), so stamp content is a build artifact of the control, not a diff.

Methodology

Environment: the verify lane's own container, Node v22.23.2, ~247 GB host (availableMemoryMb 253266, cgroup unconstrained → availableMemorySource: host), V8 heap_size_limit 4345298944 B, 64 cores. Base control: git worktree add tmp/base-tree HEAD^1 with a symlink-farm node_modules re-pointing @qwen-code/{acp-bridge,qwen-code,sdk} into the base tree (realpath-asserted both directions) plus the nested third-party node_modules; acp-bridge, sdk, and cli rebuilt there from base sources (base stamp d5e47709a3), worktree removed after the cells were captured. Harnesses (all mock-free, in harness/): wire-ab.mjs (5 cells, real daemons, real HTTP), session-probe.mjs (real child spawn, argv read from /proc with a PPid check against the daemon, held SSE stream, loopback fake-OpenAI decoy that counts requests), module-matrix.mjs (compiled dist modules driven directly, incl. the delta band), mutation-summary.mjs + mutation-followup.mjs (12 mutants applied as exact single-occurrence replacements, suites run red on named behavioral assertions, files restored via git checkout --, tree asserted clean at the end). Targeted gates: full acp-bridge suite and the five affected cli suites, run unmutated. Raw payloads: logs/cells/*.json, logs/probe-*-status.json, logs/probe-*-argv.json; logs: logs/build-base.log, logs/mutation-details.log. Evidence captures via scripts/verify-capture.mjs. Assertions: wire-ab 54 + session 18 + 15 + module 29 + mutation 18 = 134, all scripted, all executed, all passed.

Evidence images

01-wire-ab-head-vs-base

02-session-probe-observe

03-session-probe-off

04-module-matrix

05-mutation-matrix

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@wenshao

wenshao commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on a909ff5a1414f128af7dc732efa9562aae85299d is a COMMENTED one, which carries no vote — so it has no verdict of its own on this commit, and main needs two approving reviews: an approval left by another account is a separate vote and does not count as the bot's own. Two different things look like this, and the stage-3 comment above says which: the triage skill deferring on purpose at 3/5 — a fork refactor hitting the approval guardrail, or a core change escalated for maintainer awareness, both normal outcomes — or an earlier approval that a push dismissed, leaving only the comment behind, which needs a fresh review.

⚠️ 机器人在 a909ff5a1414f128af7dc732efa9562aae85299d 上唯一的评审是 COMMENTED不带票 —— 因此它在该 commit 上没有自己的裁决,而 main 需要两个批准(其他账号的批准是另一张票)。有两种情况长这样,上方的 stage-3 评论会说明是哪一种:triage skill 在 3/5 时有意 defer(fork refactor 命中审批护栏,或核心改动被升级交由维护者把关,两者都是正常结果);或者更早的批准被一次推送作废、只剩下这条评论,此时需要重新评审。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@wenshao

wenshao commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on a909ff5a1414f128af7dc732efa9562aae85299d is a COMMENTED one, which carries no vote — so it has no verdict of its own on this commit, and main needs two approving reviews: an approval left by another account is a separate vote and does not count as the bot's own. Two different things look like this, and the stage-3 comment above says which: the triage skill deferring on purpose at 3/5 — a fork refactor hitting the approval guardrail, or a core change escalated for maintainer awareness, both normal outcomes — or an earlier approval that a push dismissed, leaving only the comment behind, which needs a fresh review.

⚠️ 机器人在 a909ff5a1414f128af7dc732efa9562aae85299d 上唯一的评审是 COMMENTED不带票 —— 因此它在该 commit 上没有自己的裁决,而 main 需要两个批准(其他账号的批准是另一张票)。有两种情况长这样,上方的 stage-3 评论会说明是哪一种:triage skill 在 3/5 时有意 defer(fork refactor 命中审批护栏,或核心改动被升级交由维护者把关,两者都是正常结果);或者更早的批准被一次推送作废、只剩下这条评论,此时需要重新评审。

The stage comments above were updated with the latest result. View workflow run.

上方各阶段评论已更新为最新结果。查看工作流运行

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the updated revision — still strictly observation-only, and the refinements are an improvement: the mode flag is now just off|observe (the earlier enforce draft was a dead switch and is removed, explicitly 'no forced GC, no eviction, no session closure, no process termination'); --child-heap-mode off publishes null partition (maxConcurrentChildren/perChildCeilingMb both null); the added source semantics (RSS-vs-cgroup vs V8 heap_size_limit, rssRatio as a lower bound under host source) make the two denominators' meaning precise. The reclamation keywords appear only in docs describing what it does NOT do. No new P0/P1; my earlier note (calibrate the observe thresholds from this phase's data before any future enforcement) stands.

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 2eb5cd6 Aug 7, 2026
159 of 162 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.21.8.

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.

4 participants