Skip to content

refactor: deepen architecture ownership boundaries - #9682

Open
DragonnZhang wants to merge 28 commits into
QwenLM:mainfrom
DragonnZhang:dragon/architecture-governance
Open

refactor: deepen architecture ownership boundaries#9682
DragonnZhang wants to merge 28 commits into
QwenLM:mainfrom
DragonnZhang:dragon/architecture-governance

Conversation

@DragonnZhang

@DragonnZhang DragonnZhang commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR deepens ownership boundaries across five high-churn architecture seams while preserving existing public protocols and user-visible behavior. It centralizes bounded ACP transport safety, gives workspace-qualified routes one trusted-runtime selection policy, removes an obsolete CLI compatibility layer in favor of owning package boundaries, centralizes Web Shell prompt-admission and retry decisions, and gives WebUI session attachment a single lifecycle owner.

Why it's needed

These responsibilities were previously duplicated or spread across large composition roots, which made safety rules and concurrency invariants difficult to audit. The refactor improves locality, removes divergent implementations, and makes future changes less likely to bypass transport limits, workspace trust checks, stale retry ownership, or session-load generation checks.

Reviewer Test Plan

How to verify

  1. Confirm bounded ACP channels retain the existing queue, byte, active-handler, error-redaction, and one-shot transport-retirement behavior for oversized, cyclic, accessor-backed, and failed values.
  2. Confirm workspace-qualified trusted routes still select registered identifiers and portable absolute paths, return the existing mismatch/runtime-unavailable/trust errors, and do not invoke operations for untrusted workspaces.
  3. Confirm CLI startup and fast-path imports resolve directly through narrow ACP package boundaries while the existing CLI public exports remain available.
  4. Confirm prompt admission distinguishes definite rejection, uncertain transport outcome, and post-admission failure, and that retry state cannot commit after the user switches sessions or transcript ownership changes.
  5. Confirm session load, resume, attach, supersede, timeout, same-session refresh, and effect cleanup preserve their current behavior and stale loads cannot settle the current attachment.

Evidence (Before & After)

N/A — this is a behavior-preserving internal refactor with no intended UI changes.

Tested on

OS Status
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

Environment (optional)

Node.js v24.18.0 on macOS. The full repository build, workspace typecheck, and repository lint passed. Focused validation passed for the ACP bridge (795 tests), affected workspace routes and CLI bridge wiring (219 tests), Web Shell admission/retry behavior (496 tests), and WebUI attachment/provider behavior (337 tests). The final diff passed whitespace validation and two clean self-audit passes.

Risk & Scope

  • Main risk or tradeoff: This is a broad multi-package refactor, so the main risk is a missed concurrency or import-ownership edge despite unchanged external contracts; existing integration suites and new focused state-transition tests cover the migrated seams.
  • Not validated / out of scope: Windows/Linux local execution, UI E2E recording, transcript replay redesign, SSE projection changes, Express middleware reordering, and daemon runtime disposal changes.
  • Breaking changes / migration notes: None. REST, ACP, SSE, React context, and CLI public contracts are intended to remain unchanged.

Linked Issues

N/A

中文说明

这个 PR 做了什么

这个 PR 在保持现有公共协议和用户可见行为不变的前提下,深化了五个高频变更架构接缝的职责边界:集中管理有界 ACP 传输安全策略,为 workspace-qualified 路由提供统一的可信运行时选择策略,移除过时的 CLI 兼容层并直接依赖所属 package 边界,集中管理 Web Shell 的 prompt admission 与 retry 决策,并让 WebUI session attachment 拥有单一生命周期管理者。

为什么需要它

这些职责此前存在重复实现,或者散落在大型组合根中,导致安全规则和并发不变量难以审计。本次重构提升了职责局部性,删除了可能逐渐分叉的重复实现,并降低后续改动绕过传输限制、workspace trust 检查、过期 retry ownership 或 session load generation 校验的风险。

Reviewer 测试计划

如何验证

  1. 确认有界 ACP channel 对超大值、循环值、带 accessor 的值和失败值仍保留现有的队列、字节数、活跃 handler、错误脱敏和一次性 transport retirement 行为。
  2. 确认 workspace-qualified 可信路由仍支持已注册标识符和可移植绝对路径,继续返回现有的 mismatch、runtime unavailable 和 trust 错误,并且不会为不可信 workspace 调用实际操作。
  3. 确认 CLI 启动和 fast path import 通过窄化的 ACP package 边界直接解析,同时现有 CLI 公共导出仍然可用。
  4. 确认 prompt admission 能区分明确拒绝、传输结果不确定和 admission 后失败,并且在用户切换 session 或 transcript ownership 改变后,retry 状态不能提交。
  5. 确认 session load、resume、attach、supersede、timeout、同 session refresh 和 effect cleanup 保持现有行为,且过期 load 无法结算当前 attachment。

证据(Before & After)

N/A——这是保持行为不变的内部重构,没有预期 UI 变化。

测试平台

OS 状态
🍏 macOS
🪟 Windows N/A
🐧 Linux N/A

环境(可选)

在 macOS 和 Node.js v24.18.0 下完成验证。整仓构建、workspace typecheck 和 repository lint 均通过。聚焦验证包括 ACP bridge(795 个测试)、受影响的 workspace route 和 CLI bridge wiring(219 个测试)、Web Shell admission/retry 行为(496 个测试),以及 WebUI attachment/provider 行为(337 个测试)。最终 diff 通过 whitespace 校验和两轮干净的自审。

风险与范围

  • 主要风险或权衡:这是一次跨多个 package 的较宽重构,主要风险是在外部 contract 不变的情况下遗漏并发或 import ownership 边界;现有 integration suite 和新增的聚焦状态转换测试覆盖了迁移的接缝。
  • 未验证 / 范围外:Windows/Linux 本地执行、UI E2E 录制、transcript replay 重设计、SSE projection 变更、Express middleware 重排以及 daemon runtime disposal 变更。
  • Breaking change / 迁移说明:无。REST、ACP、SSE、React context 和 CLI 公共 contract 均应保持不变。

关联 Issue

N/A

Live bug fixes included in this refactor

Two of the ownership-boundary changes double as fixes for bugs present on main today:

  1. Outbound estimator ownership — the estimator seam now carries the correct ownership context (flagged in maintainer verification round 1).
  2. Superseded restore settling the successor's pending load — on main, pendingLoadToResolve is keyed on session identity alone, so two overlapping restores of the same session share the session object and a superseded run can resolve the successor's pending load (the newer session switch reports itself complete while its own load/resume is still in flight). The attemptedLoadForIteration guard in DaemonSessionProvider.tsx prevents this; pinned by the R9-1 fixture in DaemonSessionProvider.test.tsx (does not let a superseded restore settle the successor pending load, ea7e945), which fails on main's session-identity-only adoption.

@DragonnZhang
DragonnZhang force-pushed the dragon/architecture-governance branch from 508a4c6 to 8469565 Compare August 21, 2026 15:53
@github-actions

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)为单个提交。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head ea7e945. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

⚠️ No preview: one or more scenarios failed to render on this head — see the workflow run. This is not "no visual change" — a scenario that times out or throws produces no image. Fix the failing scenario (or a genuine regression it caught) and the preview returns on the next push.

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 1422d79, 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 12 scenario(s).

Qwen Code · serve A/B

# Conflicts:
#	packages/cli/src/serve/acp-http/dispatch.ts
#	packages/cli/src/serve/acp-session-bridge.ts
#	packages/cli/src/serve/server.ts
CI (ubuntu-latest, Node 22.x) hit the 15s test timeout on
'times out a legacy update check while its cache refresh stalls'
(run 32500060626). Install fake timers only after the harness is up
so app setup's real async work never runs under a frozen clock, and
give the test a 30s budget for the shared-runner contention this
package's vitest config already documents.
… baseline

QwenLM#9587 (b5fbdb2) grew cd-cua-driver.yml from 29715 to 42519 bytes on
main without updating the ratchet, so every PR that merges main now
fails the workflow-size check. Record the shipped size, following the
same fix as QwenLM#9747 for qwen-autofix.yml.
DragonnZhang added a commit to DragonnZhang/qwen-code that referenced this pull request Aug 23, 2026
The workflow grew from 29715 to 42519 bytes when QwenLM#9587 landed on main
without a baseline update (a latent main-side violation every PR merging
main now trips, as the check message itself directs). Record the new
size following the same remedy as QwenLM#9747/QwenLM#9682.
DragonnZhang added a commit to DragonnZhang/qwen-code that referenced this pull request Aug 23, 2026
Same latent main-side violation as QwenLM#9682/QwenLM#9683-era fixes: QwenLM#9587 grew the
workflow without a baseline update; record the new size as the check
message directs (precedent QwenLM#9747).
DragonnZhang added a commit to DragonnZhang/qwen-code that referenced this pull request Aug 23, 2026
Same latent main-side violation as fixed in QwenLM#9682: QwenLM#9587 grew the
workflow without a baseline update; record the new size as the check
message directs (precedent QwenLM#9747).
pull Bot pushed a commit to bhardwajRahul/qwen-code that referenced this pull request Aug 24, 2026
* refactor(core): shrink content generator interface

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

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

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

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

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

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

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

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

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

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

Follow-up to 18f08c0: the test plan still required converter/tokenizer
focused tests for image paths; the image-tokenizer estimator cluster was
removed in PR QwenLM#9676 (text tokenizer is unaffected and out of scope here).
# Conflicts:
#	packages/web-shell/client/App.tsx
@DragonnZhang
DragonnZhang marked this pull request as ready for review August 24, 2026 16:00
@DragonnZhang
DragonnZhang enabled auto-merge August 24, 2026 16:00
@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

⚠️ Qwen Triage ended earlyview run. It stopped before finishing; check the run log.

⚠️ Qwen Triage 提前结束 —— 查看运行。未跑完,请查看运行日志。

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Re-run at the new head (5be8b5dd) — gate re-checked, still passing.

Template looks good ✓ — all sections present, bilingual body included.

Problem: unchanged from the prior pass and still concrete, not theoretical — four near-identical trusted-runtime resolver clones across the workspace routes, ACP transport budgets split between two modules, a 159-line CLI re-export shim in front of the owning package, and duplicated admission/retry boolean pairs in Web Shell. Since the last review the author also landed the round-1 fixes (the estimator regression repair, exemption-release pinning, and the R9-1 load-identity guard), which this pass re-gated.

Direction: aligned. Consolidating the daemon's safety seams (bounded transport, workspace trust selection, prompt admission, attachment epochs) keeps those invariants auditable in one place each, and the committed design doc states the behavioral invariants up front. The follow-up stages from the same design doc have since been landing as separate PRs, which is the staging this one argued for.

Size: core infrastructure (cross-package: acp-bridge, cli, web-shell, webui). At this head: 2,740 production lines vs 1,190 test lines vs 95 docs lines across 90 files. Author is a maintainer, so the large-refactor hard block does not apply. Still well past the 1,000-line advisory — noted for awareness only; the maintainer has already said the next pass lands staged.

Approach: each seam still reads as a faithful move plus dedup rather than a rewrite — the new modules expose decisions and transitions (budgets, trust selection, attempt classification, load epochs), not React setters or Express responses. No drive-by changes spotted at gate level.

Risk: no matches against the revert-correlated high-risk paths.

Moving on to code review. 🔍

中文说明

在新 head(5be8b5dd)上重跑——门控复查,仍然通过。

模板完整 ✓ —— 各部分齐全,含中文正文。

问题: 与上一轮相同,仍是具体存在的重复而非理论问题——workspace 路由中四份几乎相同的 trusted-runtime resolver 克隆、ACP 传输预算分散在两个模块、CLI 侧 159 行的 re-export shim 挡在所属 package 前面、Web Shell 中重复的 admission/retry 布尔对。自上次审查以来,作者还合入了第一轮修复(估算器回归修复、豁免释放钉测、R9-1 load 身份守卫),本轮对其重新做了门控检查。

方向: 对齐。集中管理 daemon 的安全接缝(有界传输、workspace trust 选择、prompt admission、attachment epoch)使这些不变量可在各自唯一位置被审计,且随 PR 提交的设计文档预先声明了行为不变量。同一设计文档的后续阶段此后已按独立 PR 陆续合入,正是本 PR 主张的分阶段方式。

规模: 核心基础设施(跨 4 个 package:acp-bridgecliweb-shellwebui)。当前 head:90 个文件中生产代码 2,740 行、测试 1,190 行、文档 95 行。作者是维护者,大规模重构硬封锁不适用。仍远超 1,000 行建议阈值——仅作提示;维护者已表示下一轮将分阶段合入。

方案: 每个接缝仍是忠实的搬移加去重,而非重写——新模块暴露的是决策与状态迁移(预算、trust 选择、尝试分类、load epoch),而不是 React setter 或 Express response。门控层面未发现顺手改动。

风险: 未命中与 revert 相关的高风险路径。

进入代码审查。🔍

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Code review (re-run at 5be8b5dd)

Re-read the full diff, including everything landed since the last pass: the R1-1 estimator repair, the exemption-release pinning, the Web Shell restore-block port, the R9-1 load-identity guard, and the rebases onto main. No blockers found — the seams remain faithful moves plus dedup, and the fixes are correct.

Seam 1 — transport safety. I verified the monotonicity claim directly against the deleted code: estimateTransportValueBytes is the old outbound estimator with Object.hasOwn(v, 'toJSON') replaced by hasStructuralToJSONHazard(v), and the hazard test (callable or accessor-backed own toJSON) is a strict subset of the old refusal condition. The refusal set can only shrink, and the byte arithmetic is otherwise unchanged — so the outbound relaxation can admit plain-data toJSON values but never mis-size one, and inbound admission for wire-legal (JSON.parse'd) params is preserved. That is exactly the shape of the round-1 regression fix, and the new transport-safety.test.ts pins it mutant-style: byte parity with JSON.stringify for data-toJSON shapes, continued refusal of callable/accessor toJSON (including the array case where the hazard check is the sole defense), cycle refusal with a bounded walk, and admission-level tests for the release pairing and the undefinednull reservation normalization. The one-shot retirement, message-observation release, and guard mapping in spawnChannel.ts/bridge.ts are unchanged.

Seam 2 — trusted workspace routes. Audited every migration: each resolveWorkspaceRuntimeFromParamresolveTrustedRuntime swap deletes a same-site requireTrustedWorkspaceRuntime check, so no route newly 403s and none loses its trust gate; the two files that keep the untrusted resolver do so for the intentionally permissive read paths. Response codes (workspace_mismatch, workspace_runtime_unavailable, untrusted_workspace) are untouched.

Seam 3 — CLI shim deletion. Mechanical re-pointing of ~30 importers to the owning @qwen-code/acp-bridge subpaths; the public serve barrel keeps the same export set (factories, the error-class subset sendBridgeError matches, and the type aliases), now sourced from the owning modules, with the load-bearing-rationale comment restored. Fast-path import-boundary tests were updated to match the shim's deletion rather than weakened.

Seam 4 — prompt admission. The eight extracted helpers are faithful moves, and the classifyFailure truth table reproduces the old admissionStarted/admitted pairs exactly at all four call sites ('unknown' ⇔ started ∧ ¬admitted ∧ ¬definitelyRejected; 'after-admission' ⇔ admitted). Ownership is evaluated live at each guard, so a retry still cannot commit after a session switch.

Seam 5 — attachment lifecycle. Load identity and generation guards everywhere settle/reject/release happens: a superseded load cannot settle the current attachment, the watchdog releases before invoking onTimeout, and the R9-1 changes are correctly scoped — the iteration-local attemptedLoadForIteration stops a superseded iteration from adopting the successor's pending load (with the documented no-load-capture bypass that creation flows rely on), and releaseCleanupDetachExemption now requires the preserving load's identity so a late release from a superseded switch cannot clear the successor's exemption. Both of my prior-round judgment calls are now settled: the estimator delta is confirmed as a fix (the maintainer's seam-by-seam A/B below independently found the outbound relaxation stops a real child-kill on main), and the unconditional-clear shape is unreachable from production (its single call site passes both arguments) while the new guard is strictly tighter than main's.

Files changed (14 of 90 shown)
File What changed
packages/acp-bridge/src/transport-safety.ts New module owning budgets, byte estimation (with the hazardous-toJSON repair), log-safe client, guarded connection proxy
packages/acp-bridge/src/bridge.ts Safety code lifted out; binds the new module at the two adapter points
packages/acp-bridge/src/spawnChannel.ts Budget and teardown wiring collapsed into createBoundedAcpTransportSafety
packages/acp-bridge/src/transport-safety.test.ts New: estimator byte-parity and refusal cases, admission release pairing, null-normalization
packages/cli/src/serve/acp-session-bridge.ts Deleted: the 159-line CLI-local re-export shim
packages/cli/src/serve/workspace-route-runtime.ts resolveTrustedRuntime added as the single trusted-selection policy
packages/cli/src/serve/index.ts Public serve re-exports preserved, sourced from owning package subpaths
packages/cli/src/serve/fast-path.test.ts Import-boundary assertions updated for the shim deletion
packages/web-shell/client/prompt-admission.ts New: attempt classifier plus transcript identity helpers moved verbatim
packages/web-shell/client/App.tsx Four send/retry sites adapted to PromptAdmissionAttempt
packages/web-shell/client/prompt-admission.test.ts New: classifier truth table, identity matching, stale-owner cases
packages/webui/src/daemon/session/attachment-lifecycle.ts New: pending-load epochs, manual-clear flag, load-tied cleanup-detach exemption
packages/webui/src/daemon/session/DaemonSessionProvider.tsx Scattered refs replaced by one lifecycle instance; R9-1 adoption guard
docs/design/architecture-deepening-2026-08-21.md Design doc: invariants, module direction, staging, non-goals
…and 76 more files Route migrations, test re-homes, type-only import moves, developer-doc updates

Test evidence (PR's own CI at 5be8b5dd — no PR code executed in this run)

Every substantive check on this head is green, including Test (ubuntu-latest) — the release-note-classification failure the author flagged on the earlier head (inherited from main at the time) is no longer red here. The macOS/Windows unit-matrix and CLI-sandbox integration checks are skipped by workflow design on this event, same as prior heads. Standout signals: Serve A/B re-ran base-vs-head on this head with zero response differences, Real daemon E2E and web-shell E2E smoke both pass, and the Security Checks bundle (CVE audit, TruffleHog) is clean.

Two notes on attribution. (1) The web-shell visuals preview still reports scenario render failures on this head — this PR touches zero files under client/e2e/ (verified against the file list), and the maintainer reproduced the same two failures identically on the merge-base arm, pinning them to a pre-existing selector-strictness bug in the visuals spec on main; the capture job itself is green. (2) After the bot's deferral, maintainer @wenshao ran an independent local A/B at this exact head (200k-value estimator oracle, 489 live route probes, compiler-API export enumeration, exhaustive admission truth table) and posted his seam-by-seam results in this thread — that is his verification evidence, converging with this static review, and he has approved.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Serve A/B (ubuntu-latest, Node 22.x) success
Real daemon E2E / Java 11 success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Capture web-shell visuals (ubuntu-latest, Node 22.x) success
Desktop Shell (windows-2022) success
Desktop Shell (ubuntu-22.04) success
Integration Tests (no-AK, No Sandbox) success
precheck-pr / precheck success
Dependency CVE audit success
Secret scan (TruffleHog) success
Classify PR / label success

Sandboxed verification is already in flight: the verify job of this very triage run is executing now and will post its A/B report here on completion — no fresh trigger needed. tmux real-scenario run: N/A — unattended CI path, and this is a behavior-preserving internal refactor with nothing user-visible to drive.

中文说明

代码审查(在 5be8b5dd 上重跑)

完整重读了 diff,包括上轮之后新增的全部内容:R1-1 估算器修复、豁免释放钉测、Web Shell restore 块移植、R9-1 load 身份守卫,以及多次对 main 的 rebase。未发现阻塞项——各接缝仍是忠实的搬移加去重,且修复均正确。

接缝 1 —— 传输安全。 我直接对照被删除的旧代码验证了单调性论断:estimateTransportValueBytes 就是旧出站估算器把 Object.hasOwn(v, 'toJSON') 换成 hasStructuralToJSONHazard(v),而危险判定(自有 toJSON 可调用或为 accessor)是旧拒绝条件的真子集。拒绝集只会缩小,字节算术其余部分完全一致——因此出站放宽只可能放行纯数据 toJSON 值,不可能误估大小,入站对 wire 合法(JSON.parse 产物)参数的准入也得以保持。这正是第一轮回归修复的形状,且新的 transport-safety.test.ts 以杀变异方式钉住:数据型 toJSON 形态与 JSON.stringify 字节对齐、可调用/accessor toJSON 仍被拒绝(含危险检查是唯一防线的数组场景)、循环拒绝且遍历有界、以及准入层的释放配对与 undefinednull 预留归一化测试。spawnChannel.ts/bridge.ts 中的一次性退休、消息观测释放与 guard 映射均未改变。

接缝 2 —— 可信 workspace 路由。 逐一审计了全部迁移:每处 resolveWorkspaceRuntimeFromParamresolveTrustedRuntime 替换都同时删除了同位置的 requireTrustedWorkspaceRuntime 检查,因此没有路由新增 403,也没有路由失去 trust 门;保留未信任解析器的两个文件对应有意放宽的只读路径。响应码(workspace_mismatchworkspace_runtime_unavailableuntrusted_workspace)未变。

接缝 3 —— CLI 兼容层删除。 约 30 个导入方机械地改指向所属 @qwen-code/acp-bridge 子路径;serve 公共 barrel 保持相同导出集(工厂、sendBridgeErrorinstanceof 匹配的错误类子集、类型别名),改为从所属模块取源,并恢复了说明其公共面作用的注释。fast-path 导入边界测试随 shim 删除做了对应更新,而非削弱。

接缝 4 —— prompt admission。 抽出的 8 个 helper 是忠实搬移,classifyFailure 真值表在全部四处调用点精确复现旧 admissionStarted/admitted 布尔对('unknown' ⇔ 已开始且未准入且非明确拒绝;'after-admission' ⇔ 已准入)。所有权在每个守卫处实时求值,切换 session 后 retry 仍无法提交。

接缝 5 —— attachment 生命周期。 所有结算/拒绝/释放位置都有 load 身份与 generation 守卫:被取代的 load 无法结算当前 attachment;watchdog 在调用 onTimeout 之前释放;R9-1 改动范围正确——迭代局部的 attemptedLoadForIteration 阻止被取代的迭代认领后继者的 pending load(保留了创建流程所依赖的、有注释说明的无 load 捕获旁路),且 releaseCleanupDetachExemption 现在要求保留方 load 的身份,被取代 switch 的迟到释放不再能清掉后继者的豁免。我上一轮的两个判断点均已落定:估算器差异被确认为修复(维护者下述逐接缝 A/B 独立发现出站放宽阻止了 main 上一个真实的子进程误杀),无条件清除形态在生产中不可达(唯一调用点两个参数都传了实值),而新守卫比 main 更严格。

测试证据(5be8b5dd 上 PR 自身 CI——本次运行未执行任何 PR 代码)

本 head 上所有实质性检查均为绿色,包括 Test (ubuntu-latest)——作者在较早 head 上指出的 release-note 分类失败(当时继承自主干)在此已不再为红。macOS/Windows 单测矩阵与 CLI 沙箱集成检查按工作流设计在此事件下为 skipped,与之前的 head 一致。突出信号:Serve A/B 在本 head 上重跑 base 与 head 对比,响应零差异;真实 daemon E2E 与 web-shell E2E smoke 均通过;安全检查组合(CVE 审计、TruffleHog)干净。

两点归属说明。(1)web-shell 视觉预览在本 head 上仍报告场景渲染失败——本 PR 未触碰 client/e2e/ 下任何文件(已对照文件列表核实),且维护者在 merge-base 侧复现出完全相同的两处失败,定位为干线视觉用例中既有的选择器严格性 bug;截图任务本身为绿。(2)在机器人缓议之后,维护者 @wenshao与本 head 完全一致的提交上独立做了本地 A/B(20 万值估算器 oracle、489 次实况路由探测、编译器 API 导出面枚举、穷举准入真值表),并在本线程发布了逐接缝结果——那是他的验证证据,与本静态审查相互印证,他已批准。

沙箱验证已在路上:本次 triage 运行的 verify 任务正在执行,完成后会自动在此发布 A/B 报告——无需再次触发。tmux 真实场景:不适用——无人值守 CI 路径,且本 PR 是行为保持的内部重构,没有可操作的用户可见变化。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Confidence: 3/5 — clean review, and this head now carries green CI plus a maintainer's seam-by-seam verification; the cap is pure policy — the fork-refactor guardrail keeps the final approval human.

Stepping back: my two reservations from the first pass are both resolved, and resolved in the interesting direction. The estimator swap I flagged as "a policy change embedded in the move" turned out to be the most valuable part of the PR — the round-1 fix restored inbound parity, and the maintainer's A/B then showed the outbound relaxation is monotone and stops a real child-kill on main (a client-hosted MCP tool result carrying a literal toJSON key used to retire the whole transport). The unconditional exemption clear I flagged is unreachable from production, and its replacement is strictly tighter than what it replaced. My independent read going in was "one owner module per seam, exposing decisions not plumbing" — the PR matches that, and the new tests kill the mutants that matter rather than just exercising lines.

Everything else converges: the static review at this head finds the seams faithful, the trust gates preserved one-for-one, and the public CLI surface intact; the PR's own CI is fully green on 5be8b5dd with a fresh zero-diff Serve A/B; and the maintainer's independent local verification at the same commit agrees on every seam. The size reservation stands as a record, not a blocker — 2,740 production lines across four packages is a lot to revert as a unit, and the design doc's five stages were five landable PRs; the follow-up stages have since been landing that way.

Per policy I'm still not approving: a cross-repository refactor PR takes the defer path no matter how clean the review is, and that guardrail exists precisely so a human owns the final call. No blocking issues found — no --request-changes either. @wenshao's approval already stands on this head; what remains is human-owned.

中文说明

置信度:3/5 —— 审查干净,且本 head 已有全绿 CI 与维护者逐接缝验证;封顶纯属策略——fork 重构护栏规定最终批准必须由人类做出。

退一步看:我第一轮的两个保留意见都已落定,而且落定的方向很有意思。我曾把估算器替换标记为「搬移中夹带的策略变化」,结果它成了本 PR 最有价值的部分——第一轮修复恢复了入站等价性,维护者的 A/B 随后证明出站放宽是单调的,并且止住了 main 上一个真实的子进程误杀(客户端托管 MCP 工具结果携带字面 toJSON 键曾导致整条传输被退休)。我标记的无条件豁免清除在生产中不可达,其替代实现比被替换者更严格。我入场前的独立判断是「每个接缝一个属主模块,暴露决策而非管线」——PR 与此一致,且新测试杀掉的是真正要紧的变异,而不只是跑通代码行。

其余各方相互印证:本 head 的静态审查显示各接缝忠实、trust 门一一对应保留、CLI 公共面完整;PR 自身 CI 在 5be8b5dd 上全绿,并带有新一轮零差异 Serve A/B;维护者在同一提交上的独立本地验证对每个接缝的结论一致。规模保留意见作为记录存在,不构成阻塞——跨四个 package 的 2,740 行生产代码作为整体回滚成本不低,设计文档的五个阶段本可以拆成五个可分别合入的 PR;后续阶段此后正是这样合入的。

按策略我仍不做批准:跨仓库(fork)的 refactor PR 无论审查多干净都走缓议路径,这条护栏的存在正是为了让最终决定由人类做出。未发现阻塞问题——因此也不提交 --request-changes@wenshao 的批准已在本 head 上;剩余事项由人类掌握。

Qwen Code · qwen3.8-max

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

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

⏸️ Deferring to @wenshao — re-run at 5be8b5dd: the review is clean (no blockers found across all five seams; the round-1 fixes verified correct), CI is fully green on this head, and your seam-by-seam local verification already covers the two judgment calls from the original deferral. Per policy this stays a human approval: cross-repository refactor PRs are never bot-approved regardless of review outcome. Your approval already stands here — the remaining merge requirement (a second approving review) is human-owned, and the verify job running in this triage run will post its A/B report below when it completes.

中文说明

⏸️ 转交 @wenshao —— 在 5be8b5dd 上重跑:审查干净(五个接缝均未发现阻塞项;第一轮修复已验证正确),本 head 的 CI 全绿,你对各接缝的本地逐项验证也已覆盖最初缓议时的两个判断点。按策略,批准仍由人类做出:跨仓库(fork)的 refactor PR 无论审查结果如何都不由机器人批准。你的批准已在本 head 上——剩余的合入条件(第二个批准)由人类掌握;本次 triage 运行中的 verify 任务完成后会在下方发布其 A/B 报告。

Qwen Code · qwen3.8-max

Reviewed at 5be8b5dd43da950851135c3f5eb7e26bc06207d1 · re-run with @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.

Partially reviewed — gaps disclosed.

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

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:反向审计——在 3 轮的反审轮数上限内未收敛。

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

Comment thread packages/acp-bridge/src/transport-safety.ts
Comment thread packages/acp-bridge/src/transport-safety.ts
Comment thread packages/webui/src/daemon/session/attachment-lifecycle.ts
Comment thread packages/cli/src/serve/index.ts
Comment thread packages/cli/src/serve/workspace-agents.ts Outdated
…e estimation

The estimator unification in 8469565 silently swapped inbound handler
admission from the deleted lenient estimateAcpHandlerBytes to the strict
outbound estimator, which refused any object or array carrying an own
toJSON key regardless of its value. Inbound params are JSON.parse'd wire
data, where toJSON is plain data that JSON.stringify serializes as an
ordinary member, so the refusal retired whole daemon transports over
wire-legal messages (guard.fail destroys the child's stdio and terminates
the child in the spawnChannel wiring).

Restrict the refusal to the shapes that actually defeat structural
estimation: callable own toJSON (the JSON.stringify substitution hazard)
and accessor-backed toJSON. Plain-data toJSON keys are estimated like any
other member. Pinned by transport-safety.test.ts (byte parity with
JSON.stringify, continued callable/accessor/cyclic refusal, and
admission-level coverage); the tests fail against the blanket
Object.hasOwn check.
rejectPendingLoad releases the cleanup-detach exemption for the failed
load's session, and cancelPendingLoad routes through it; nothing in the
existing suite exercised that coupling (deleting the
releaseCleanupDetachExemptionForSession call kept all lifecycle, actions,
and provider tests green). Add reject/cancel cases asserting the promise
settles and the exemption is gone afterwards, plus a scope case keeping
another session's exemption intact. The first two fail against the
deletion mutant.
…el rationale

workspace-agents.ts re-exported InvalidClientIdError for test files, but
the same refactor re-pointed the only such test (workspace-memory.test.ts)
to import from @qwen-code/acp-bridge/bridgeErrors directly, leaving the
re-export with zero consumers and the file never uses the symbol
internally. Delete the re-export, its now-false rationale comment, and
the unused import; workspace-agents.ts is not on the CLI's public barrel,
so no external surface changes.

Conversely, restore a shortened rationale above the serve barrel's typed
error re-export block: sendBridgeError still matches every class there via
instanceof, and with all in-repo callers now importing bridgeErrors
directly the comment is the only surviving argument against a future
'remove unused exports' cleanup that would break external embeds.
@DragonnZhang

Copy link
Copy Markdown
Collaborator Author

Round-1 review response — all 5 threads verified against code, all valid

Each finding was checked against the code at 291201531b before acting. Summary: R1-1 is a genuine regression introduced by the estimator unification (fixed + pinned with tests); R1-3/R1-4/R1-5 are all accurate and addressed. Details with evidence below. Fixes: e9ffc4e (R1-1 fix + tests), 1365489 (R1-3 tests), 855c047 (R1-4 + R1-5).

R1-1 (Critical, both locations) — confirmed: real regression, fixed

The behavior-preserving claim was broken on the inbound path. Evidence trail:

  1. Pre-PR there were two estimators: inbound admission in bridge.ts used the lenient estimateAcpHandlerBytes (no toJSON refusal at all; it counted every entry via Object.entries), while the outbound budgets in spawnChannel.ts used the strict estimatePreparedResponseBytes (blanket Object.hasOwn(current, 'toJSON') refusal).
  2. This PR lifted the strict estimator as-is into transport-safety.ts (estimateTransportValueBytes is byte-identical to the old outbound estimator) and wired both paths to it — silently making inbound admission strict. The deleted lenient estimator had no surviving equivalent.
  3. Inbound params are JSON.parse'd wire values (ndJsonStream.ts:422), so an own toJSON key is plain data — never callable — and JSON.stringify serializes it as an ordinary member. The SDK zod gates don't strip it: rawInput/rawOutput are z.unknown() (@agentclientprotocol/sdk schema/zod.gen.js:1282), populated with arbitrary model/MCP JSON.
  4. The refusal converts to requiredBytes = maxActiveHandlerBytes + 1 in AcpInboundHandlerAdmission.run, so guard.fail fires — and in the spawnChannel wiring that destroys the child's stdio and terminates the child (spawnChannel.ts:275). One wire-legal message retires the whole session transport.
  5. That contradicts the design-doc invariant: only invalid, cyclic, accessor-backed, or oversized values fail closed. A plain-data toJSON key is none of those. (The stage-2 triage noted the estimator swap as a deliberate fail-closed policy delta; the admission-kill consequence for wire-legal values is what makes it a regression rather than a policy choice.)

Fix (both sites, array + record branches): hasStructuralToJSONHazard() now refuses only the shapes that actually defeat structural estimation — a callable own toJSON (the substitution hazard) or an accessor-backed one. Plain-data toJSON keys are estimated like any other member, exactly as JSON.stringify treats them. The check reads the own property descriptor, so no getter is ever invoked by the estimator itself. Note this also relaxes the pre-existing outbound blanket refusal for plain-data toJSON values; that is sound because JSON.stringify serializes such members normally, so the estimate still bounds the serialized size.

Pinned by the new transport-safety.test.ts: byte-parity with JSON.stringify for JSON-parsed shapes carrying toJSON data keys (nested record + array), continued refusal of callable/accessor toJSON and cyclic values, and admission-level tests — a wire-legal rawInput frame runs the handler exactly once without guard.fail, while a callable toJSON still fails closed. Negative control: restoring Object.hasOwn(current, 'toJSON') makes 4 of these tests fail.

R1-3 (Suggestion) — confirmed: gap was real, tests added

Verified: the lifecycle suite covered supersede, stale-settle, watchdog, and direct preserve/release, but never reject/cancel with an exemption held — deleting the releaseCleanupDetachExemptionForSession line in rejectPendingLoad keeps every existing test green (the supersede path reaches that line, but only via startPendingLoad, where no exemption is held). Added three tests to attachment-lifecycle.test.ts: reject releases the exemption, cancel releases it, and rejecting one session's load preserves another session's exemption. The first two fail against the deletion mutant.

R1-4 (Suggestion) — confirmed: rationale restored

Verified: sendBridgeError matches all nine re-exported classes via instanceof (server/error-response.ts:310,324,335,346,358,367,404,469,477), and every in-repo caller now imports from @qwen-code/acp-bridge/bridgeErrors directly, leaving the barrel block with zero in-repo consumers. Restored a shortened rationale comment above the block recording that it is load-bearing public surface for external embeds.

R1-5 (Suggestion) — confirmed: dead surface removed

Verified: the only references to InvalidClientIdError in workspace-agents.ts were the import and the re-export — no internal use; all consumers repo-wide (including workspace-memory.test.ts, which this PR re-pointed) import from @qwen-code/acp-bridge/bridgeErrors; and workspace-agents.ts is an internal route module, not part of the CLI's public barrel, so no external-embed argument applies. Deleted the re-export, the misleading comment, and the unused import.

Additional verification (open item from the triage deferral): web-shell visuals render failure

Not caused by this PR. The visuals workflow (run 32737065411) renders both the PR base and this head in one job: the workspace sidebar scenario (dark + light) fails identically in the base checkout (base/packages/web-shell/client/e2e/visuals/screenshots.spec.ts:834, strict-mode violation — 'Run auth migration' resolves to two session rows) as in the head (2 failed / 30 passed on base; 2 failed / 35 passed on head, same two tests). This PR's entire web-shell diff vs main is the prompt-admission seam (App.tsx send/retry sites + prompt-admission.ts); it does not touch sidebar or session-list rendering. This is a pre-existing failure on main that the visuals harness surfaced, and it should be tracked separately.

@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.

Partially reviewed — gaps disclosed. 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: reverse audit — did not converge within the reverse-audit round cap of 5.

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:78 — [probe] timeout test asserts only post-conditions; release-before-callback ordering unpinned — deferred under the round-2 code-age rule (anchored on code unchanged since th…
中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

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

Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/webui/src/daemon/session/attachment-lifecycle.test.ts
Comment thread packages/cli/src/serve/index.ts Outdated

@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.

Partially reviewed — gaps disclosed. 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: reverse audit — did not converge within the reverse-audit round cap of 10.

Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:

  • packages/acp-bridge/src/transport-safety.test.ts:151 — [probe] record-cycle test pins only the refusal sentinel — the record-side seen-check mutant survives (array leg is pinned)
  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:60 — [probe] timeout test asserts only post-conditions; release-before-callback ordering unpinned
  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:94 — [probe] releaseCleanupDetachExemption identity guard never exercised with a non-matching session

Convergence: round 3 posted 5 inline comment(s), 4 of them reported for the first time; the previous round posted 9 (9 new). Findings keep coming back to the same files: packages/acp-bridge/src/transport-safety.test.ts (findings in round 2; 2 more now); packages/webui/src/daemon/session/attachment-lifecycle.test.ts (findings in round 2; 2 more now). A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. (Observation only — nothing was withheld from this review because of this observation.)

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:reverse audit — did not converge within the reverse-audit round cap of 10。

收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 3 轮发布了 5 条行内评论,其中 4 条是首次提出;上一轮发布了 9 条(其中 9 条首次提出)。发现反复回到同一批文件:packages/acp-bridge/src/transport-safety.test.ts(第 2 轮已出过发现,本轮又有 2 条);packages/webui/src/daemon/session/attachment-lifecycle.test.ts(第 2 轮已出过发现,本轮又有 2 条)。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。(仅为观察——本轮评审未因此扣留任何内容。)

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

Comment thread packages/cli/src/serve/index.ts Outdated
Comment thread packages/webui/src/daemon/session/attachment-lifecycle.test.ts
Comment thread packages/acp-bridge/src/transport-safety.test.ts Outdated
Comment thread packages/acp-bridge/src/transport-safety.test.ts
Comment thread packages/webui/src/daemon/session/attachment-lifecycle.test.ts
…dler-count/null-normalization/supersede-release

@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.

Partially reviewed — gaps disclosed.

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

Not explored to full depth (tool budget reached): "agent invariant-c (packages/acp-bridge/src/spawnChannel.ts)": none — no check was cut short..

Deferred under the convergence posture (round 4, not a blocker) — recorded, not requested in this round:

  • packages/acp-bridge/src/transport-safety.ts:223 — [probe] no test pins the observeMessage direction === 'sent' guard — the release-on-received mutant survives the whole acp-bridge suite
中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"agent invariant-c (packages/acp-bridge/src/spawnChannel.ts)"none — no check was cut short.

收敛姿态下延后(第 4 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

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

@DragonnZhang

Copy link
Copy Markdown
Collaborator Author

CI note: the only failing check is Test (ubuntu-latest) → Run .github/scripts helper tests → release note classification → "updates labels after a lookup failure and exits non-zero" (.github/scripts/classify-release-notes.test.mjs:224). This PR does not touch that script or its test — both files are byte-identical to main here — and main's own CI has failed on the same subtest for three consecutive days (ci.yml runs of 08-25, 08-26, and 08-27 are all red at the same spot). The red is inherited from main, not caused by this branch; every other check on this head is green.

中文说明:唯一失败的检查是 main 自身已连续三天在同一子测试上失败的 classify-release-notes 辅助测试;本分支未改动该脚本与测试(与 main 逐字节一致),红色继承自主干,其余检查全绿。

@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.

Partially reviewed — gaps disclosed.

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

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

Not explored to full depth (tool budget reached): chunk 4: executed the test suite in a throwaway tree ( npm ci not feasible in this worktree within budget); substituted exhaustive hand-tracing of all nine tests agains….

Deferred under the convergence posture (round 7, not a blocker) — recorded, not requested in this round:

  • packages/web-shell/client/App.tsx:6685 — [review] classifyFailure's admitted-over-definitelyRejected precedence — load-bearing for this branch's equivalence — is pinned by no test
  • packages/acp-bridge/src/transport-safety.test.ts:44 — [review] estimator number arm and undefined half are exercised by no test — two invisible mutants (probe-verified)
  • packages/acp-bridge/src/transport-safety.ts:235 — [review] any pipeLimits with maxQueuedBytes <= 2048 passes validation but kills the channel on the first guarded operation (probe-verified)
  • packages/acp-bridge/src/transport-safety.ts:590 — [review] releaseTerminal/killTerminal ?? {} normalization — load-bearing for reserve/release identity pairing — is pinned by no test (mutant-probed)
  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:316 — [review] loadless-release-vs-load-associated-exemption shape — the exact call shape all three production release sites use — is pinned by no test (mutant-probed)
  • packages/acp-bridge/src/transport-safety.ts:64 — [review] error-frame reserve→release pairing (releaseMessage's message['error'] branch) works today but is pinned by no test (probe-verified)
  • packages/acp-bridge/src/transport-safety.test.ts:254 — [review] admission budget release on the handler-exception path (the finally decrements) is pinned by no test (mutant-probed)

Convergence: round 7 posted 1 inline comment(s), 1 of them reported for the first time; the previous round posted 1 (1 new). The rate of new findings is not falling. Batching the remaining fixes and verifying them before the next push keeps the loop from re-deriving the same set; this PR's reviews already resolve to a critical posting floor. (Observation only — nothing was withheld from this review because of this observation.)

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

Residual risk: this loop is persistently critical — Criticals stood in the previous round's work-list and stand again this round (1 Critical(s)), the rate of first-time findings is not falling (this round 1, previous 1), and the standing Critical backlog is not shrinking. The severity floor will not converge it. Recommendation: land-with-residual-risk — the exit is a maintainer risk-acceptance decision (merge, carrying the residual risk), not another review round. Residual-risk inventory for that decision (maintainer to complete):

standing Critical attack surface attacker-dependency blast radius
(each standing Critical)

Advisory only — it does not block this review.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:reverse audit — did not converge within the reverse-audit round cap of 3。

未探索到全部深度(达到工具调用预算):chunk 4:executed the test suite in a throwaway tree ( npm ci not feasible in this worktree within budget); substituted exhaustive hand-tracing of all nine tests agains…

收敛姿态下延后(第 7 轮,非阻断)——已记录,本轮不要求修改:共 7 条(原文未翻译,列表见上方英文部分)。

收敛情况:第 7 轮发布了 1 条行内评论,其中 1 条是首次提出;上一轮发布了 1 条(其中 1 条首次提出)。新发现的产出速度没有下降。把剩余修复攒成一批、验证后再推送,可以避免循环反复推导同一组发现;本 PR 的评审已解析为 critical 发布下限。(仅为观察——本轮评审未因此扣留任何内容。)

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

残余风险:本循环处于 persistently-critical 形态——上一轮工作清单中的 Critical 本轮依然存在(本轮 1 条 Critical),首次发现的速率没有下降(本轮 1,上一轮 1),且未决 Critical 积压没有减少。severity floor 无法使其收敛。建议:land-with-residual-risk——出口是 maintainer 的风险接受决定(合入并承担残余风险),而非再开一轮评审。供该决定使用的残余风险清单(maintainer 填写):按每条未决 Critical 列出「攻击面 · 攻击者依赖性 · 影响范围」三栏。仅为建议——不阻断本次评审。

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

Comment thread packages/webui/src/daemon/session/actions.test.ts Outdated

@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.

Partially reviewed — gaps disclosed.

7 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R8-2 watchdog test release-before-callback ordering unpinned (attachment-lifecycle.test.ts:97) — already recorded as round-2/3/5/6/7 convergence-posture deferrals (reviews 5013080184, 5014440443, 5029145840, 5032096362, 5045295751)
  • R8-3 watchdog onTimeout swallow / load-always-settles guarantee unpinned (attachment-lifecycle.ts:72) — already recorded as the round-6 convergence-posture deferral (review 5032096362)
  • R8-8 estimator number arm and undefined half unpinned in acceptance direction (transport-safety.test.ts:50) — already recorded as the round-7 convergence-posture deferral (review 5045295751)
  • R8-9 admission budget release on the handler-exception path unpinned (transport-safety.test.ts:251) — already recorded as round-6/7 convergence-posture deferrals (reviews 5032096362, 5045295751)
  • R8-10 watchdog-timeout settle path never releases the cleanup-detach exemption, unpinned (attachment-lifecycle.ts:68) — already recorded as round-5/6 convergence-posture deferrals (reviews 5029145840, 5032096362)
  • R8-12 record-cycle test pins only the refusal sentinel — record-side seen-check mutant survives (transport-safety.test.ts:152) — already recorded as the round-3 convergence-posture deferral (review 5014440443)
  • R8-13 error-frame reserve-release pairing (release half + identity) unpinned (transport-safety.ts:64) — already recorded as round-5/7 convergence-posture deferrals (reviews 5029145840, 5045295751)

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

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

Not explored to full depth (tool budget reached): "agent invariant-a (packages/acp-bridge/src/spawnChannel.ts)": (none — no check was cut short); chunk 5: none (suite execution was skipped for the reason stated above, which is an environment constraint, not a check the ceiling stopped — every test was verified by ….

Deferred under the convergence posture (round 8, not a blocker) — recorded, not requested in this round:

  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:116 — [probe] watchdog clearTimeout on early settle unpinned by any test
  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:133 — [probe] session-identity clause of releaseCleanupDetachExemption unpinned in either direction

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 7 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — did not converge within the reverse-audit round cap of 3。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"agent invariant-a (packages/acp-bridge/src/spawnChannel.ts)"(none — no check was cut short);chunk 5:none (suite execution was skipped for the reason stated above, which is an environment constraint, not a check the ceiling stopped — every test was verified by …

收敛姿态下延后(第 8 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

…overnance

# Conflicts:
#	packages/cli/src/serve/routes/session-pr-backfill.test.ts

@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.

Partially reviewed — gaps disclosed.

5 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • fail-once transport retirement invariant unpinned (transport-safety.ts:190) — already recorded as the round-6 convergence-posture deferral (review 5032096362)
  • admission count-exhaustion branch (maxActiveHandlers term) never exercised (transport-safety.test.ts) — already recorded as the round-5 convergence-posture deferral (review 5029145840)
  • admission budget release on the handler-exception path / error-frame reserve-release positive control (transport-safety.test.ts) — already recorded as round-5/7 convergence-posture deferrals (reviews 5029145840, 5045295751)
  • watchdog-timeout settle path never releases the cleanup-detach exemption (attachment-lifecycle.test.ts) — already recorded as round-5/6 convergence-posture deferrals (reviews 5029145840, 5032096362)
  • watchdog test release-before-callback ordering unpinned (attachment-lifecycle.test.ts) — already recorded as round-2/3/5/6/7/8 convergence-posture deferrals (reviews 5013080184, 5014440443, 5029145840, 5032096362, 5045295751, 5051873342)

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

Not explored to full depth (tool budget reached): chunk 18: none — no checks were cut short..

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

Deferred under the convergence posture (round 9, not a blocker) — recorded, not requested in this round:

  • packages/acp-bridge/src/transport-safety.test.ts:334 — [probe] runRequest reservation identity unpinned — a structural-clone reservation mutant ships green (865 tests) while object-response charges leak until the queue limit retires a healt…
  • packages/acp-bridge/src/transport-safety.ts:491 — [probe] log-safe mask errorKind/status/hint preservation contract unpinned — the mask-destroying mutant ships green (804+18 tests) while child errorKind-keyed fallbacks silently stop firing
中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 5 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):chunk 18:none — no checks were cut short.

未审查:反向审计——在 3 轮的反审轮数上限内未收敛。

收敛姿态下延后(第 9 轮,非阻断)——已记录,本轮不要求修改:共 2 条(原文未翻译,列表见上方英文部分)。

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

Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx
Comment thread packages/webui/src/daemon/session/DaemonSessionProvider.tsx

@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.

No blocking issues. LGTM! ✅

Not explored to full depth (tool budget reached): "agent 1a": could not run tsc --noEmit or the webui vitest suite against the commit — the review worktree has no node_modules and a full monorepo install was not attempte….

Deferred under the convergence posture (round 10, not a blocker) — recorded, not requested in this round:

  • packages/webui/src/daemon/session/DaemonSessionProvider.tsx:1619 — [probe] R9-1 guard wiring unpinned at the provider layer
  • packages/webui/src/daemon/session/DaemonSessionProvider.tsx:1775 — [review] R9-1 comment misnames the adoption bypass dependent
  • packages/webui/src/daemon/session/DaemonSessionProvider.tsx:1773 — [review] attemptedLoadForRun is iteration-scoped, not run-scoped
中文说明

无阻断问题。LGTM!✅

未探索到全部深度(达到工具调用预算):"agent 1a"could not run tsc --noEmit or the webui vitest suite against the commit — the review worktree has no node_modules and a full monorepo install was not attempte…

收敛姿态下延后(第 10 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

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

@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.

Partially reviewed — gaps disclosed.

1 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R11-1 R9-1 provider-layer guard wiring unpinned (DaemonSessionProvider.tsx:1780) — already recorded as the round-10 convergence-posture deferral (review 5055463393)

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

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 1 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

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

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — built a real local environment and A/B'd every seam against the merge-base

Verified at head 5be8b5dd43 against merge-base 4ef780cbfa, Linux x86_64 / Node v22.22.2, both arms fully built in separate worktrees. I went after the two judgment calls the triage bot deferred to me, plus the open question about the web-shell visual render failures.

Verdict: the behavior-preserving claim holds. Approving. Two of the deltas are not just acceptable — they fix live bugs on main. Details and evidence below.

Seam 1 verification


1. The inbound estimator (deferred call #1) — regression is genuinely fixed, and the outbound side fixes a main bug

I extracted all three estimators — the merge-base lenient inbound estimateAcpHandlerBytes, the merge-base strict outbound estimatePreparedResponseBytes, and the PR's unified estimateTransportValueBytes — and ran them side by side with real JSON.stringify as the oracle.

200,000 wire-shaped values (generated, then round-tripped through JSON.stringify/JSON.parse, i.e. exactly what the inbound path receives):

property result
values the new estimator refuses 0 / 200,000
byte under-counts vs real JSON.stringify 0 / 200,000
verdict differs from the old inbound estimator 0 / 200,000
verdict differs from the old outbound estimator 50,831 / 200,000 — all in the admit direction

So R1-1 is fixed, at scale, not just for the one shape in the regression test. 27 hand-built adversarial shapes agree (cycles, shared DAGs, getters, setters, callable and accessor toJSON, Date, class instances, bigint, symbol keys, non-enumerable own keys).

The structural argument is stronger than the sampling: estimateTransportValueBytes is the old outbound estimator with Object.hasOwn(v, 'toJSON') replaced by hasStructuralToJSONHazard(v), which is a strict subset of it. The change is therefore monotone — it can only ever refuse fewer values, never mis-size one.

The outbound relaxation is reachable, and it fixes a live availability bug on main. I drove a real createSpawnChannelFactory spawning a real NDJSON child, wired through the real ClientSideConnection + createLogSafeAcpClient + createOutboundGuardedConnection exactly as bridge.ts does:

scenario merge-base PR head
inbound session/update carrying a plain-data toJSON alive alive
inbound fs/read_text_file carrying a data toJSON alive alive
prepared response carrying a data toJSON transport retired, child SIGTERM'd (exit 143) alive
client-hosted MCP tool result forwarded via extMethod transport retired, child SIGTERM'd (exit 143) alive

That last row is the production shape, not a synthetic one: BridgeClient.handleClientMcpMessage ends in return { payload: response }, where response is the JSON-RPC reply an extension's client-hosted MCP server sent back over the daemon WebSocket. On main, an MCP tool result containing a member literally named toJSON — a perfectly legal JSON key, and one an extension author controls — raises NdJsonQueueLimitError, destroys stdin/stdout and terminates the agent child. This PR is what stops that.

I'd call this a fix worth naming in the PR body rather than leaving it filed as "a policy change embedded in the move".

Non-blocking residual, pre-existing on both arms: both estimators only inspect own properties, so Object.prototype.toJSON pollution under-counts (measured: est 30 vs real 500,002 bytes). main has the identical blind spot via Object.hasOwn. Not introduced here; noting it so it is on the record.


2. releaseCleanupDetachExemption(undefined) (deferred call #2) — unreachable, and the new guard is stricter than main

The unconditional-clear path the bot flagged is not reachable from production code. releaseCleanupDetachExemption has exactly one non-test call site (actions.ts:572), and it passes both currentSession and pendingLoad. The optional-parameter shape is API surface on a new internal class, not a live behavior.

More importantly, the PR's guard is a strict subset of main's clears, and closes a real gap. Main compares session identity only (skipNextCleanupDetachSessionRef.current === currentSession), which cannot distinguish two overlapping reloads of the same session, because they share the session object. Runtime A/B of that exact interleaving:

two overlapping reloads of the SAME session; the superseded load's late .finally() releases
  MERGE-BASE   exemption still held for the in-flight reload B: NO   <-- superseded load cleared the successor
  PR-HEAD      exemption still held for the in-flight reload B: YES

Also pinned, since it was recorded as a deferred probe in an earlier round: the watchdog path does release the pending load before invoking onTimeout (pendingLoad observed as undefined from inside the callback). And stale-load isolation holds — a superseded load cannot settle the current attachment; supersede rejects it with AbortError.


3. Everything else, A/B'd rather than read

Seams 2-5 and gates

Seam 2 — workspace-qualified trusted routes. Booted two real daemons (node packages/cli/dist/index.js serve), folder trust enabled, two registered workspaces — one TRUST_FOLDER, one DO_NOT_TRUST. Probed 80 workspace-qualified routes × 6 selectors (registered id, absolute path, unknown id, non-absolute garbage; trusted and untrusted), plus 9 well-formed requests against the untrusted workspace so the trust gate is reached rather than short-circuited by param validation. 489 probes, 0 differences in status code or error code. 63/80 routes answer 403 untrusted_workspace; 76/80 answer 400 workspace_mismatch for an unknown selector — identically on both arms. This goes past the CI Serve A/B's 12 scenarios in the direction that matters for this seam: the negative and trust paths.

Unchanged-by-this-PR observation: /file, /file/bytes, /stat, /list, /glob answer 200 on an untrusted workspace on both arms. That is pre-existing read-only-browsing policy, not something this PR introduces — flagging only so the 200s in my matrix are not misread.

Seam 3 — CLI shim deletion. Enumerated the export surface of packages/cli/src/serve/index.ts on both arms with the TypeScript compiler API (no module execution, no build dependency): 120 symbols, identical, zero diff. No file still imports acp-session-bridge.

Seam 4 — Web Shell prompt admission. The 8 extracted helpers (retryOwnerMatchesCurrent, getLatestUserBlock, matchesUserMessageIdentity, findUserMessageByIdentity, getRetryableTurnError, matchesTurnErrorIdentity, retryTranscriptIdentityMatches, getLatestUserBlockId) are faithful moves from App.tsx: 7 of the 8 are byte-identical, and getRetryableTurnError differs only by an iindex loop-variable rename plus a single-statement brace collapse. For PromptAdmissionAttempt I transcribed main's branch expressions and ran an exhaustive truth table over (started × admitted × definitelyRejected): 8/8 states produce identical branch decisions for the unknown-admission branch, the restore branch and the not-started guard. All four call sites checked individually — including the multi-line uncertain-admission condition at the sendPrompt site, which folds into classifyFailure(...) === 'unknown' correctly. The ownership gate is evaluated live at each guard, so a retry cannot commit after a session switch.

Seam 5 — WebUI attachment lifecycle. Covered above.

Gates. npm run typecheck across the whole repo: 11 errors on merge-base, the identical 11 on PR head (all in @qwen-code/external-context-mem0, an ajv import-style drift; untouched by this PR). Worth stating explicitly because ci.yml does not run tsc, so this is the one gate a PR of this size could otherwise slip past. Unit tests on PR head: acp-bridge 1812/1812, web-shell 4423/4423, webui 651/651.


4. The web-shell visual render failures are not caused by this PR

This was the open question in the deferral. I reproduced the CI symptom locally and ran the same suite on both arms:

merge-base PR head
web-shell visual capture (37 scenarios) 35 pass / 2 fail 35 pass / 2 fail

Same two scenarios, same error, on both arms — workspace sidebar (dark and light), failing with:

strict mode violation: getByRole('complementary').getByText('Run auth migration')
resolved to 2 elements

That is a selector-strictness bug in packages/web-shell/client/e2e/visuals/screenshots.spec.ts:778 — the loaded session's name renders both in the attention group and in its per-workspace group, so the non-exact getByText matches twice. The PR touches no file under client/e2e/. No /verify needed; this is a pre-existing failure on main that suppresses the visuals preview for every PR touching web-shell, and it deserves its own small fix.


Summary

seam claim verdict
1 · bounded ACP transport behavior-preserving ✅ inbound preserved (0/200k diffs); outbound relaxation is monotone, sound, and fixes a live child-kill bug on main
2 · trusted workspace routes behavior-preserving ✅ 489 live probes, 0 differences
3 · CLI shim deletion public exports preserved ✅ 120 symbols identical
4 · prompt admission behavior-preserving ✅ faithful moves + 8/8 exhaustive truth table
5 · attachment lifecycle behavior-preserving ✅ preserved, and the exemption guard is strictly tighter than main

Two follow-ups, neither blocking this PR:

  1. Fix screenshots.spec.ts:778's ambiguous locator so the visuals preview stops being suppressed repo-wide.
  2. Consider calling out the MCP-tool-result transport-kill fix in the PR body — it is a user-visible fix hiding inside a refactor, and it is the kind of thing worth finding in git log later.

On size: 2,959 production lines across four packages in one PR is a lot to revert as a unit, and the design doc's five stages really were five landable PRs. I'm merging this one, but let's land the next pass staged.

中文版报告

维护者验证 —— 已在本地搭建真实环境,逐个接缝与 merge-base 做 A/B

在 head 5be8b5dd43 与 merge-base 4ef780cbfa 上验证,Linux x86_64 / Node v22.22.2,两侧分别在独立 worktree 中完整构建。重点攻了 triage 机器人转交给我的两个判断点,外加 web-shell 视觉渲染失败这个悬而未决的问题。

结论:行为保持的说法成立,予以合并。 其中两处 delta 不只是可以接受 —— 它们修掉了 main 上的真实 bug。

1. 入站估算器(转交判断点 #1)—— 回归确已修复,且出站侧修掉了 main 的一个 bug

我把三个估算器全部抽出来并排跑:merge-base 宽松的入站 estimateAcpHandlerBytes、merge-base 严格的出站 estimatePreparedResponseBytes,以及本 PR 统一后的 estimateTransportValueBytes,用真实 JSON.stringify 作为 oracle。

20 万条 wire 形态的值(生成后经 JSON.stringify/JSON.parse 往返,即入站路径实际收到的形态):

指标 结果
新估算器拒绝的值 0 / 200,000
相对真实 JSON.stringify 的字节低估 0 / 200,000
与旧入站估算器判定不一致 0 / 200,000
与旧出站估算器判定不一致 50,831 / 200,000 —— 全部朝“放行”方向

即 R1-1 确实被修复了,而且是在规模上成立,不只是回归测试里那一个形态。另有 27 个手工构造的对抗形态(循环、共享 DAG、getter、setter、可调用与访问器 toJSONDate、类实例、bigint、symbol 键、不可枚举自有键)结论一致。

结构性论证比抽样更强:estimateTransportValueBytes 就是旧出站估算器把 Object.hasOwn(v, 'toJSON') 换成了 hasStructuralToJSONHazard(v),而后者是前者的真子集。因此这个改动是单调的 —— 只可能拒绝更少的值,不可能把某个值的大小算错。

出站放宽是可达的,而且它修掉了 main 上一个真实的可用性 bug。 我驱动了真实的 createSpawnChannelFactory 去 spawn 一个真实的 NDJSON 子进程,并完全按 bridge.ts 的方式接上真实的 ClientSideConnection + createLogSafeAcpClient + createOutboundGuardedConnection

场景 merge-base PR head
入站 session/update 携带纯数据 toJSON 存活 存活
入站 fs/read_text_file 携带数据 toJSON 存活 存活
prepared response 携带数据 toJSON 传输被退休,子进程被 SIGTERM(exit 143) 存活
客户端托管 MCP 工具结果经 extMethod 转发 传输被退休,子进程被 SIGTERM(exit 143) 存活

最后一行是生产形态而非人为构造:BridgeClient.handleClientMcpMessage 的结尾是 return { payload: response },其中 response 是扩展所托管的 MCP server 经 daemon WebSocket 回传的 JSON-RPC 应答。在 main 上,只要某个 MCP 工具结果里含有一个字面名为 toJSON 的成员 —— 这是完全合法的 JSON 键,且由扩展作者控制 —— 就会抛出 NdJsonQueueLimitError,销毁 stdin/stdout 并终止 agent 子进程。是这个 PR 止住了它。

我认为这值得在 PR 正文里明确写成一处修复,而不是仅仅归档为「搬移中夹带的策略变更」。

补充(非阻塞,两侧均已存在):两个估算器都只检查自有属性,所以 Object.prototype.toJSON 污染会导致低估(实测:估算 30 字节 vs 实际 500,002 字节)。main 经由 Object.hasOwn 有完全相同的盲点。并非本 PR 引入,仅作记录。

2. releaseCleanupDetachExemption(undefined)(转交判断点 #2)—— 不可达,且新守卫比 main 更严格

机器人标记的无条件清除路径在生产代码中不可达。releaseCleanupDetachExemption 只有一个非测试调用点(actions.ts:572),且两个参数都传了实值(currentSessionpendingLoad)。可选参数只是新内部类的 API 形状,不构成实际行为。

更重要的是,PR 的守卫是 main 清除集合的真子集,并且补上了一个真实缺口。main 只比较 session 身份(skipNextCleanupDetachSessionRef.current === currentSession),无法区分同一个 session 的两次重叠 reload,因为它们共享同一个 session 对象。对这一交错场景的运行时 A/B:

同一 session 的两次重叠 reload;被取代的那次 load 的迟到 .finally() 执行释放
  MERGE-BASE   仍在飞行中的 reload B 的豁免是否保留:否   <-- 被取代的 load 清掉了后继者的豁免
  PR-HEAD      仍在飞行中的 reload B 的豁免是否保留:是

另外,因为前几轮把它记成了 deferred probe,这里一并钉住:watchdog 路径确实在调用 onTimeout 之前释放了 pending load(在回调内部观测到 pendingLoadundefined)。stale-load 隔离同样成立 —— 被取代的 load 无法结算当前 attachment,取代动作会以 AbortError reject 它。

3. 其余部分:用 A/B 验证,而非阅读代码

接缝 2 —— workspace-qualified 可信路由。 启动两个真实 daemon(node packages/cli/dist/index.js serve),开启 folder trust,注册两个 workspace —— 一个 TRUST_FOLDER,一个 DO_NOT_TRUST。探测 80 条 workspace-qualified 路由 × 6 种选择子(已注册 id、绝对路径、未知 id、非绝对路径垃圾值;可信与不可信各一组),另加 9 个针对不可信 workspace 的格式完整请求,以确保真正触达 trust 门而不是被参数校验提前短路。489 次探测,状态码与错误码 0 差异。 63/80 条路由返回 403 untrusted_workspace;对未知选择子 76/80 条返回 400 workspace_mismatch —— 两侧完全一致。这比 CI Serve A/B 的 12 个场景更进一步,且进的正是这个接缝该覆盖的方向:否定路径与信任路径。

补充(与本 PR 无关):/file/file/bytes/stat/list/glob 在两侧都对不可信 workspace 返回 200。这是既有的只读浏览策略,并非本 PR 引入 —— 特此说明,以免我矩阵里的 200 被误读。

接缝 3 —— CLI 兼容层删除。 用 TypeScript 编译器 API 在两侧枚举 packages/cli/src/serve/index.ts 的导出面(不执行模块、不依赖构建):120 个符号,完全一致,零差异。已无任何文件 import acp-session-bridge

接缝 4 —— Web Shell prompt admission。 抽出的 8 个 helper(retryOwnerMatchesCurrentgetLatestUserBlockmatchesUserMessageIdentityfindUserMessageByIdentitygetRetryableTurnErrormatchesTurnErrorIdentityretryTranscriptIdentityMatchesgetLatestUserBlockId)是从 App.tsx 忠实搬移:其中 7 个逐字节一致,getRetryableTurnError 的差异仅为 iindex 循环变量重命名,外加一处单语句大括号折叠。对 PromptAdmissionAttempt,我把 main 的分支表达式转写出来,做了 (started × admitted × definitelyRejected) 的穷举真值表8/8 个状态下,unknown 分支、restore 分支与 not-started 守卫的判定完全一致。四处调用点逐个核对,包括 sendPrompt 处那个多行的 uncertain-admission 条件,它折叠成 classifyFailure(...) === 'unknown' 是正确的。所有权门在每个守卫处实时求值,因此用户切换 session 后 retry 无法提交。

接缝 5 —— WebUI attachment lifecycle。 见上文。

各项门禁。 全仓 npm run typecheckmerge-base 11 个错误,PR head 同样的 11 个(全部位于 @qwen-code/external-context-mem0,属 ajv import 风格漂移;本 PR 未触碰)。之所以要明确写出来,是因为 ci.yml 并不跑 tsc,对这种体量的 PR 而言这是唯一可能被绕过的门禁。PR head 单测:acp-bridge 1812/1812、web-shell 4423/4423、webui 651/651。

4. web-shell 视觉渲染失败不是本 PR 造成的

这是转交时留下的开放问题。我在本地复现了 CI 症状,并在两侧跑了同一套用例:

merge-base PR head
web-shell 视觉截图(37 个场景) 35 通过 / 2 失败 35 通过 / 2 失败

两侧是同样的两个场景、同样的报错 —— workspace sidebar(dark 与 light),报错为:

strict mode violation: getByRole('complementary').getByText('Run auth migration')
resolved to 2 elements

这是 packages/web-shell/client/e2e/visuals/screenshots.spec.ts:778 的选择器严格性 bug —— 已加载 session 的名称同时渲染在 attention 分组和它所属的 workspace 分组里,因此非 exactgetByText 匹配到了两个。本 PR 未触碰 client/e2e/ 下的任何文件。无需 /verify;这是 main 上既有的失败,它会让每个改动 web-shell 的 PR 都拿不到视觉预览,值得单独修一下。

小结

接缝 声称 结论
1 · 有界 ACP 传输 行为保持 ✅ 入站保持(20 万条 0 差异);出站放宽单调、可靠,且修掉 main 上一个会杀死子进程的真实 bug
2 · 可信 workspace 路由 行为保持 ✅ 489 次实况探测,0 差异
3 · CLI 兼容层删除 公共导出不变 ✅ 120 个符号完全一致
4 · prompt admission 行为保持 ✅ 忠实搬移 + 8/8 穷举真值表
5 · attachment lifecycle 行为保持 ✅ 保持,且豁免守卫比 main 更严格

两个后续项,均不阻塞本 PR:

  1. 修掉 screenshots.spec.ts:778 的歧义选择器,让视觉预览不再被全仓性地压制。
  2. 考虑在 PR 正文中点名 MCP 工具结果导致传输被杀这处修复 —— 这是藏在重构里的用户可见修复,日后在 git log 里能被找到才有价值。

关于体量:一个 PR 跨 4 个 package、2,959 行生产代码,作为一个整体回滚的代价偏高,设计文档里的五个阶段本来确实可以拆成五个 PR。这一个我合了,但下一轮请分阶段落地。

wenshao
wenshao previously approved these changes Aug 29, 2026
@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

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

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (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: 115 passed · 1 failed · 116 total

Flakiness gate: ⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

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

脚本断言:115 通过 · 1 失败 · 116 总计

抖动门:⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

Verification report

PR 9682 verification — refactor: deepen architecture ownership boundaries

Verdict: findings — 115/116 scripted assertions passed; 1 fail = a surviving
mutation (Suggestion-level, see F1). Verified head: 5be8b5dd43da950851135c3f5eb7e26bc06207d1
(base 5ae363e2f9067168f52f9d82d3be351a17aed1ce). Assertion totals: pass=115 fail=1 total=116.

中文摘要
  • 判定: findings(115/116 断言通过;唯一失败是 F1 的存活突变,属 Suggestion 级)。
  • A/B 结论(中心主张): 传输安全集中化保持行为不变。24 个 wire 合法 payload 在 head 与 base 的准入判定完全一致(24/24),head 估计值对 JSON.stringify 实际字节数全部 sound(≥ actual),其中纯字符串/容器 payload 达到逐字节相等(11/11)。e9ffc4e 修复类(plain-data toJSON)在 base 的严格 outbound 估计器下全部被拒(即中间态回归类),head 全部放行且字节相等(8/8)。14 个危险形状(callable/accessor toJSON、accessor 成员/索引、循环、非 plain 值等)head 全部 fail-closed;base 宽松估计器对其中 12 种放行且会调用 accessor getter(副作用),head 不调用。准入阈值扫描:head 在 actual+2048 envelope 处精确放行(9410),base 宽松版在 9010 即放行(低于实际字节数,属 base 的 unsafe 行为,head 收紧方向正确)。
  • 突变矩阵: transport-safety.test.ts 的 5 个突变全部被杀且归属正确(含 e9ffc4e 回退突变杀 3 个测试);webui R6-1 双层守卫两个半区各自被杀(lifecycle 关联守卫 + actions 传 load);reject/cancel 豁免释放的正向对照杀 2 个新测试;prompt-admission 两个分支均被杀。
  • Findings: F1(Suggestion)R9-1 的 attemptedLoadForIteration 守卫在 provider 全部 248 个测试下存活(web-shell 4449 个测试因解析 webui 的 dist 而无法看到该突变,不计入证据);F2 为观察项(预先存在的 NUL 字节,非本 PR 引入);F3 为完整性报告(R6-1 三个 successor 测试中只有一个直接钉住关联守卫,另两个钉住 session-keyed 路径,守卫整体由 lifecycle+actions 两个测试共同钉住)。
  • 未覆盖: 逐 commit 归因(depth-2 shallow,中间 commit 不可达);web-shell 对 base 的行为 A/B(base 逻辑内嵌于 App.tsx,无独立驱动面);resolveTrustedRuntime 的 HTTP 级 A/B(机械组合,已被 122 个 cli serve 测试覆盖);integration 套件、Windows 路径、UI E2E、SSE/daemon disposal(PR 自述范围外)。

Central claim + A/B

Central claim: the transport-safety centralization is behavior-preserving —
wire-legal inbound values keep their base admission behavior with byte parity
to JSON.stringify, hazardous shapes keep failing closed, and the
guard/budget/retirement semantics are unchanged.

Method: head arm = compiled packages/acp-bridge/dist/transport-safety.js;
base arm = a HEAD^1 worktree running the base's own TypeScript via a vitest
probe whose only change was adding export to the two base estimators
(verified by git diff in the worktree). Wire oracle = JSON.stringify over
24 round-tripped payloads; 15 constructed hazard shapes built identically on
both arms. Witness: evidence/01-ab-transport-safety-head-vs-base.png
(raw log: logs/ab-transport-safety.log).

cell oracle base head verdict
wire admit parity (24 payloads) both estimators finite at generous limit 24/24 finite 24/24 finite parity holds
soundness (head ≥ JSON.stringify bytes) wire oracle n/a 24/24 holds (worst: number 24 vs 1)
exact byte parity (string/container payloads) head === actual n/a 11/11 holds
e9ffc4e class (plain-data toJSON, 4 shapes) strict-outbound refuses / head admits refuses 4/4 (regression class) admits 4/4, parity fix present
hazard refusal (14 shapes @​1024) head === 1025 lenient admits 12/14 (unsound census) refuses 14/14 fail-closed preserved
accessor side effect getter call count calls getters (1/1) 0 calls tightening justified
admission threshold (escape-heavy 7362 B) min limit admitting 9010 (< actual+2048, unsound) 9410 (= actual+2048) safe tightening
admission-level cells adm1–adm9 (dist) handler/fail counters code-identical except estimator 9/9 holds

The base lenient estimator's census (admits callable toJSON, calls accessor
getters, admits below actual wire bytes) is the justification for head's
strictness; every head-vs-base disagreement on wire data is in the safe
direction, and on non-wire hazards head is stricter exactly where
JSON.stringify substitution would make base's estimate unsound.

Secondary claims verified:

  • CLI serve barrel: 52/52 value exports identical base↔head (none missing,
    none added); createOutboundGuardedConnection, method set, and both budget
    classes diff to only the export keyword.
  • R6-1 exemption guard: layered pair, each half load-bearing (W1/W2 below).
  • Prompt admission classification: both branches pinned (P1/P2).

Findings

F1 (Suggestion) — R9-1's attempted-load guard is not pinned by any test

Reverting the guard added in the R9-1 commit — replacing
(attemptedLoadForIteration === undefined || pendingLoad === attemptedLoadForIteration)
with true in packages/webui/src/daemon/session/DaemonSessionProvider.tsx
(~line 1782) — leaves the full provider suite green:
Tests: 248 passed (248), exit 0. The guard is live code (it changes which
pending load an attach iteration may resolve when a newer iteration has
started one), so this is a coverage gap, not dead code.

Reproduce:

cd packages/webui && python3 - <<'EOF'
F='src/daemon/session/DaemonSessionProvider.tsx'; s=open(F,encoding='utf-8').read()
open(F,'w',encoding='utf-8').write(s.replace("(attemptedLoadForIteration === undefined ||\n              pendingLoad === attemptedLoadForIteration)","true"))
EOF
npx vitest run src/daemon/session/DaemonSessionProvider.test.tsx src/daemon/session/DaemonSessionProvider.subagent.test.ts
# then restore the file

Caveat on the sweep: the web-shell suite (4449 tests) also stayed green, but
its vitest config has no alias for @qwen-code/webui, so it resolves the
built dist — that run never saw the mutant and is not evidence; the
provider suite (same-package source) is the valid oracle. A fixture that
would pin the guard: two consecutive attach-loop iterations where iteration N
captures its own load, then a newer iteration's startPendingLoad supersedes
it before N reaches the resolve point — with the guard reverted, N would
resolve/adopt the wrong generation.

F2 (observation, pre-existing, not introduced by this PR) — NUL bytes in DaemonSessionProvider.tsx

DaemonSessionProvider.tsx line 327 embeds two literal U+0000 bytes in the
userBlockBoundaryKey template literal. The identical bytes exist at base
(HEAD^1), so the PR preserved them verbatim; they are a deliberate
collision-free separator. Side effect: the file classifies as binary to
grep/file, which is why tooling skips it. Not a finding against this PR;
flagged only so a future cleanup knows.

F3 (completeness, not a merge condition) — R6-1 pinning set is a pair

With the load-association guard deleted (W1), only 1 of the 3 successor
lifecycle tests fails ("keeps the successor's exemption when a superseded
same-session release runs late", assertion expected false to be true); the
other two exercise the session-keyed release path, which stays correct. The
guard is nonetheless pinned end-to-end because the actions-level test
("keeps the successor exempted when a same-session reload supersedes") also
dies under W1 and under W2 (actions stops passing pendingLoad) — each half
of the layered guard is independently load-bearing.

Mutation matrix

evidence/02-mutation-matrix-transport-safety.png (raw: logs/matrix-transport-safety.log),
evidence/03-webui-r6-mutation-matrix.png.

mutant suite result killed by
M0 control transport-safety.test.ts (18) GREEN
M1 revert e9ffc4e (blanket toJSON refusal) same RED 3/18 failed the 2 plain-data toJSON estimator tests + admission test (values: refusal sentinel vs parity bytes; admission rejects instead of resolving)
M2 drop callable check same RED 1 "still refuses callable toJSON values"
M3 drop accessor get/set check same RED 1 "still refuses accessor-backed toJSON values"
M4 delete boolean arm same RED 1 "estimates boolean and JSON null values on their dedicated arms"
M5 drop frame-level record accessor check (positive control) same RED 1 "refuses accessor-backed members and indices at the frame level"
W0 control lifecycle+actions (119) GREEN
W1 drop load-association guard same RED 2 actions successor test + lifecycle late-release test
W2 actions stops passing pendingLoad same RED 1 actions successor test
W3 drop exemption release on reject (positive control) same RED 2 the two reject/cancel exemption tests
P1/P2 drop after-admission / unknown branch prompt-admission.test.ts (5) RED 1 each "distinguishes rejection, unknown outcome, after-admission failure"
R9-1 guard → true provider suites (248) GREEN survivor → F1

Targeted gates (all green at head)

gate result
packages/acp-bridge full suite 35 files, 1812 tests passed
packages/webui src/daemon/session 11 files, 453 tests passed
packages/web-shell prompt-admission + App 2 files, 562 tests passed
packages/web-shell full suite (supplementary) 4449 tests passed
packages/cli serve targeted (route-runtime, fast-path, default-bridge-wiring) 122 tests passed
repo npm run typecheck exit 0

Not covered

  • Per-commit attribution: the checkout is depth-2; only the merge commit,
    base tip, and PR head are reachable (git rev-list HEAD^1..HEAD^2 = 1 vs
    25 commits in the metadata). All evidence is against the aggregate
    HEAD^1..HEAD diff.
  • Web-shell prompt-admission A/B against base: the base logic was embedded in
    App.tsx React state with no independently drivable surface; covered by
    the new unit tests (P0–P2) and the 562-test gate instead.
  • resolveTrustedRuntime HTTP-level A/B: the route diffs are mechanical
    recombinations of the same two calls (resolveWorkspaceRuntimeFromParam +
    requireTrustedWorkspaceRuntime → shared resolveTrustedRuntime); covered
    by the 122 cli serve tests including workspace-route-runtime.test.ts.
  • Integration suites, Windows/UNC path behavior, UI E2E, SSE projection,
    daemon runtime disposal — unchanged per the PR's own scope statement.
  • Two harness oracle corrections happened mid-round (a parity tag list that
    included a number-valued payload, and a redaction oracle stricter than the
    verbatim-moved base contract); both were harness bugs, fixed and re-run —
    the shipped counts are from the final run.

Methodology

Environment: CI merge-ref checkout (HEAD = merge, HEAD^1 = base,
HEAD^2 = PR head), node v22.23.2, builds pre-existing. Head arm imported
the compiled dist/transport-safety.js; base arm ran in a scratch worktree
at HEAD^1 whose only diff was two export keywords (quoted in the report's
methodology note; worktree removed after capture). Internal workspace
confound checked: @qwen-code/qwen-code-core is untouched by the PR, so
root-node_modules sharing cannot contaminate the estimators, which never
call core code. Harnesses, corpus, and raw logs live in this artifact
directory (ab-transport-safety.mjs, ab-lib.mjs, matrix-transport-safety.mjs,
matrix-webui.py, logs/); evidence PNGs in evidence/ were produced by
scripts/verify-capture.mjs.

Flakiness gate log

rounds=5 files=36 skipped=0
file packages/acp-bridge/src/transport-safety.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/transport-safety.test.ts
file packages/cli/src/serve/acp-http/dispatch-error.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/dispatch-error.test.ts
file packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/workspace-qualified-acp.test.ts
file packages/cli/src/serve/daemon-status.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/daemon-status.test.ts
file packages/cli/src/serve/fast-path.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/fast-path.test.ts
file packages/cli/src/serve/multi-workspace-sessions.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/multi-workspace-sessions.test.ts
file packages/cli/src/serve/routes/session-pr-backfill.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-pr-backfill.test.ts
file packages/cli/src/serve/routes/session-prompt-terminals.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-prompt-terminals.test.ts
file packages/cli/src/serve/routes/session-telemetry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-telemetry.test.ts
file packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-extensions-controller.test.ts
file packages/cli/src/serve/routes/workspace-git-branches.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-branches.test.ts
file packages/cli/src/serve/routes/workspace-git-diff.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-diff.test.ts
file packages/cli/src/serve/routes/workspace-git-log.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-log.test.ts
file packages/cli/src/serve/routes/workspace-git.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git.test.ts
file packages/cli/src/serve/routes/workspace-github-prs.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-github-prs.test.ts
file packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-qualified-extensions.test.ts
file packages/cli/src/serve/routes/workspace-setup-github.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-setup-github.test.ts
file packages/cli/src/serve/server-acp-child-extra-args.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server-acp-child-extra-args.test.ts
file packages/cli/src/serve/server-default-bridge-wiring.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server-default-bridge-wiring.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/cli/src/serve/server/device-flow-registry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/device-flow-registry.test.ts
file packages/cli/src/serve/server/session-archive.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/session-archive.test.ts
file packages/cli/src/serve/session-id-admission.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/session-id-admission.test.ts
file packages/cli/src/serve/total-session-admission.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/total-session-admission.test.ts
file packages/cli/src/serve/workspace-agents.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-agents.test.ts
file packages/cli/src/serve/workspace-generation.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-generation.test.ts
file packages/cli/src/serve/workspace-git-state.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-git-state.test.ts
file packages/cli/src/serve/workspace-memory.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-memory.test.ts
file packages/cli/src/serve/workspace-qualified-rest.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-qualified-rest.test.ts
file packages/cli/src/serve/workspace-registry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-registry.test.ts
file packages/cli/src/serve/workspace-remember.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-remember.test.ts
file packages/cli/src/serve/workspace-route-runtime.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-route-runtime.test.ts
file packages/cli/src/serve/workspace-service/__tests__/integration.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-service/__tests__/integration.test.ts
file packages/web-shell/client/prompt-admission.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/prompt-admission.test.ts
file packages/webui/src/daemon/session/actions.test.ts: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/actions.test.ts
file packages/webui/src/daemon/session/attachment-lifecycle.test.ts: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/attachment-lifecycle.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/transport-safety.test.ts: PP
  packages/cli/src/serve/acp-http/dispatch-error.test.ts: PP
  packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: PP
  packages/cli/src/serve/daemon-status.test.ts: PP
  packages/cli/src/serve/fast-path.test.ts: PP
  packages/cli/src/serve/multi-workspace-sessions.test.ts: PP
  packages/cli/src/serve/routes/session-pr-backfill.test.ts: PP
  packages/cli/src/serve/routes/session-prompt-terminals.test.ts: PP
  packages/cli/src/serve/routes/session-telemetry.test.ts: PP
  packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: PP
  packages/cli/src/serve/routes/workspace-git-branches.test.ts: P
  packages/cli/src/serve/routes/workspace-git-diff.test.ts: P
  packages/cli/src/serve/routes/workspace-git-log.test.ts: P
  packages/cli/src/serve/routes/workspace-git.test.ts: P
  packages/cli/src/serve/routes/workspace-github-prs.test.ts: P
  packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: P
  packages/cli/src/serve/routes/workspace-setup-github.test.ts: P
  packages/cli/src/serve/server-acp-child-extra-args.test.ts: P
  packages/cli/src/serve/server-default-bridge-wiring.test.ts: P
  packages/cli/src/serve/server.test.ts: P
  packages/cli/src/serve/server/device-flow-registry.test.ts: P
  packages/cli/src/serve/server/session-archive.test.ts: P
  packages/cli/src/serve/session-id-admission.test.ts: P
  packages/cli/src/serve/total-session-admission.test.ts: P
  packages/cli/src/serve/workspace-agents.test.ts: P
  packages/cli/src/serve/workspace-generation.test.ts: P
  packages/cli/src/serve/workspace-git-state.test.ts: P
  packages/cli/src/serve/workspace-memory.test.ts: P
  packages/cli/src/serve/workspace-qualified-rest.test.ts: P
  packages/cli/src/serve/workspace-registry.test.ts: P
  packages/cli/src/serve/workspace-remember.test.ts: P
  packages/cli/src/serve/workspace-route-runtime.test.ts: P
  packages/cli/src/serve/workspace-service/__tests__/integration.test.ts: P
  packages/web-shell/client/prompt-admission.test.ts: P
  packages/webui/src/daemon/session/actions.test.ts: P
  packages/webui/src/daemon/session/attachment-lifecycle.test.ts: P

verdict: timeout
summary: the 15-minute budget elapsed before two full rounds completed (1 done) — no flakiness signal either way

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/transport-safety.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/daemon-status.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/fast-path.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/multi-workspace-sessions.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-pr-backfill.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-prompt-terminals.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-telemetry.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-branches.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-diff.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-log.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-github-prs.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-setup-github.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server-acp-child-extra-args.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server-default-bridge-wiring.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/device-flow-registry.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/session-archive.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/session-id-admission.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/total-session-admission.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/workspace-agents.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/workspace-generation.test.ts: P (exit 0)
round 1 · packa

...truncated -- full content in the run artifacts.

Evidence images

01-ab-transport-safety-head-vs-base

02-mutation-matrix-transport-safety

03-webui-r6-mutation-matrix

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

Qwen Code · sandboxed verification

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Triage re-run completed without a new review.

⚠️ The bot's only review on 5be8b5dd43da950851135c3f5eb7e26bc06207d1 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.

⚠️ 机器人在 5be8b5dd43da950851135c3f5eb7e26bc06207d1 上唯一的评审是 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.

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

…overnance

# Conflicts:
#	packages/cli/src/serve/acp-session-bridge.ts

@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.

Partially reviewed — gaps disclosed.

4 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R12-1 R9-1 adoption guard unpinned (DaemonSessionProvider.tsx:1786) — already reported as the round-10 convergence-posture deferral (review 5055463393) and round-11 R11-1
  • R12-2 watchdog test release-before-callback ordering unpinned (attachment-lifecycle.test.ts:99) — already reported as round-2/3/5/6/7/8 convergence-posture deferrals (reviews 5013080184, 5014440443, 5029145840, 5032096362, 5045295751, 50518…
  • R12-3 notification no-reservation invariant unpinned (transport-safety.test.ts:243) — already reported as round-5/6 convergence-posture deferrals (reviews 5029145840, 5032096362)
  • R12-4 watchdog onTimeout swallow / load-always-settles guarantee unpinned (attachment-lifecycle.ts:73) — already reported as the round-6 deferral (review 5032096362) and round-8 R8-3

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

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

Deferred under the convergence posture (round 12, not a blocker) — recorded, not requested in this round:

  • packages/web-shell/client/prompt-admission.ts:150 — [probe] turn-error selection and identity helpers unpinned by any test
  • docs/developers/daemon/03-acp-bridge.md:269 (+1 locations) — [probe] added doc sentences overstate the subpath migration
  • packages/webui/src/daemon/session/attachment-lifecycle.test.ts:285 — [probe] resolve/reject cleanup-detach exemption asymmetry unpinned
  • packages/acp-bridge/src/transport-safety.ts:86 — [probe] retained-message count arm unpinned in both budgets
  • packages/acp-bridge/src/transport-safety.ts:135 — [probe] outbound overflow rejection untested
中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 4 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — did not converge within the reverse-audit round cap of 3。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

收敛姿态下延后(第 12 轮,非阻断)——已记录,本轮不要求修改:共 5 条(原文未翻译,列表见上方英文部分)。

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

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification, round 2 — re-built the environment at the new head 1422d79b22

The head moved after my last pass (5be8b5dd431422d79b22, a fresh merge of main), and the sandboxed /triage run came back with one open finding. So I rebuilt both arms — PR head 1422d79b22 and its merge-base 5c47cf95bc — in separate fully-built worktrees on Linux x86_64 / Node v22.22.2, and went after three things: (1) the sandbox's F1, (2) whether the merge silently dropped anything main landed in PR-touched files, (3) re-running every seam A/B at the new pair rather than carrying my old numbers forward.

Verdict: still merging it. And F1 turns out to be the most interesting thing on this PR — the unpinned guard is not just live code, it fixes a bug that is on main today. Fixture below, verified three ways.

R9-1 attempted-load guard


1. F1 resolved — the R9-1 guard is load-bearing, and main has the defect it prevents

The sandbox run reported that reverting the attemptedLoadForIteration guard in DaemonSessionProvider.tsx leaves the provider suite green. Confirmed at this head, and wider: with the guard replaced by true, the entire packages/webui suite — 36 files, 654 tests — stays green. So it is a genuine coverage gap, not a sandbox artifact.

I then instrumented the real provider at the adoption point and re-ran the whole suite to see why nothing kills it:

state at the guard evaluations
pendingLoad's session ≠ active session → guard irrelevant 365
session matches, iteration captured the same load → guard passes 20
session matches, iteration captured no load → the documented creation-flow bypass 5
discriminating — captured load ≠ current pendingLoad 0 / 390

The branch the guard exists for is never entered by any existing test. So I built the fixture that enters it. The interleaving is: a restore is in flight; the successor's startPendingLoad runs synchronously inside the action, while the stale run's continuation is a microtask that was queued first — so the stale run wakes up past its abort check with pendingLoad already replaced.

arm guard result
PR head 1422d79b22 present ✅ passes
PR head, guard → true reverted expected 'resolved' to be undefined
merge-base 5c47cf95bc (i.e. main) absent expected 'resolved' to be undefined

That third row is the point. The fixture only drives the public actions surface (resumeSession), so it drops onto main unchanged — and fails there. On main, pendingLoadToResolve is keyed on session identity alone:

// main
const pendingLoadToResolve =
  pendingLoad?.sessionId === activeSession.sessionId ? pendingLoad : undefined;

Two overlapping restores of the same session share the session object, so a superseded run resolves the successor's pending load. User-visible shape: the newer session switch reports itself complete while its own load/resume is still in flight — the caller's promise settles on a restore that was never theirs.

So this is a second live-bug fix hiding inside the refactor, alongside the outbound-estimator one I flagged last time. Worth naming in the PR body.

The fixture, ready to paste into DaemonSessionProvider.test.tsx (whole packages/webui suite with it added: 655/655, and repo typecheck clean):

R9-1 pinning fixture
  // R9-1 regression fixture: a superseded restore run must not settle the
  // successor's pending load. The stale run's `client.resume()` settles in the
  // microtask queued BEFORE the successor registers its own load, so the stale
  // run reaches the adoption point while `attachmentLifecycle.pendingLoad` is
  // already the successor's. Without the attempted-load guard it resolves the
  // successor's promise even though the successor's own restore never settled.
  it('does not let a superseded restore settle the successor pending load', async () => {
    vi.stubGlobal(
      'fetch',
      vi.fn(async () => new Response(null, { status: 204 })),
    );
    const sessionA = createMockSession({
      sessionId: 'session-a',
      events: createIdleEvents(),
    });
    sdkMocks.sessions.push(sessionA);
    let actions: DaemonSessionActions | undefined;
    function Harness() {
      actions = useDaemonActions();
      return null;
    }
    await renderWithProvider(<Harness />, { autoConnect: true });
    await act(async () => {
      await flushPromises();
    });

    const staleRestore = createDeferred<MockSession>();
    const neverSettles = createDeferred<MockSession>();
    sdkMocks.MockDaemonSessionClient.resume
      .mockImplementationOnce(async () => staleRestore.promise)
      .mockImplementationOnce(async () => neverSettles.promise);

    let firstOutcome: string | undefined;
    let secondOutcome: string | undefined;
    act(() => {
      void requireActions(actions)
        .resumeSession('session-b')
        .then(
          () => {
            firstOutcome = 'resolved';
          },
          () => {
            firstOutcome = 'rejected';
          },
        );
    });
    await act(async () => {
      await flushPromises();
    });

    const sessionB = createMockSession({
      sessionId: 'session-b',
      events: createIdleEvents(),
      replaySnapshot: createTextReplaySnapshot('session b transcript'),
    });

    await act(async () => {
      // Queue the stale run's continuation first, then register the successor
      // load synchronously — the stale run wakes up with pendingLoad already
      // replaced.
      staleRestore.resolve(sessionB);
      void requireActions(actions)
        .resumeSession('session-b')
        .then(
          () => {
            secondOutcome = 'resolved';
          },
          () => {
            secondOutcome = 'rejected';
          },
        );
      await flushPromises();
      await flushPromises();
      await flushPromises();
    });

    // The first switch was superseded, so it rejects with AbortError.
    expect(firstOutcome).toBe('rejected');
    // The successor's own restore never settled, so nothing may settle it.
    expect(secondOutcome).toBeUndefined();

    await act(async () => {
      neverSettles.reject(new Error('cleanup'));
      root?.unmount();
      root = null;
      await flushPromises();
    });
  });

2. The merge is clean — checked, not assumed

main moved 15 commits under this branch since my last pass, three of them touching files this PR rewrites. All three survived the merge intact:

main commit state at head
#9955 fix(core): expose resolved agent execution mode present — isTaskExecutionMode wired into the provider's subagent projection
#9895 feat(daemon): support scoped workspace memory tasks present — workspace-remember.ts carries main's version
#9406 feat(serve): hide workspace Browse on headless daemon hosts present — nativeDirectoryPickerSupported kept in App.tsx, alongside the PR's own extraction from the same file (+59/−197 against the merge-base)

One thing the merge does drop, correctly: main added BridgeWorkspaceMemoryRememberTargetScope to the re-export list in acp-session-bridge.ts, the shim this PR deletes. That symbol was never part of the public serve/index.ts barrel, and workspace-remember.ts imports it straight from @qwen-code/acp-bridge/bridgeTypes at head. Nothing references the deleted file.

Also checked for the failure mode a refactor of this shape invites: none of the 8 extracted admission helpers is left behind as a duplicate definition in App.tsx.


3. Every seam re-measured at the new pair

Seams 1-4 and gates

Seam 1 — bounded ACP transport. transport-safety.ts is byte-identical to the head I verified before, so the estimator logic didn't move; I re-ran the differential anyway at the new pair. 200,000 wire-shaped values (generated, then round-tripped through JSON.stringify/JSON.parse), with real JSON.stringify as the oracle and both of main's estimators exported from the merge-base worktree whose only diff is two export keywords: 0 refusals, 0 byte under-counts, 0 verdict differences against main's inbound estimator, and 58,310 differences against main's strict outbound estimator — all in the admit direction. acp-bridge: 1812/1812.

Seam 2 — workspace-qualified trusted routes. This is where main's new commits landed, so it got the full treatment again rather than a carry-forward. Two real daemons (node dist/cli.js serve), folder trust on, one TRUST_FOLDER and one DO_NOT_TRUST workspace registered per arm, each arm with its own workspace pair so the mutating probes can't cross-contaminate. 80 workspace-qualified routes × 6 selectors = 480 probes per arm, 0 differences in status code or error code. 63 routes answer 403 untrusted_workspace; 76 answer 400 workspace_mismatch for an unknown or non-absolute selector. I added a runtime-readiness gate before measuring — without it the daemon answers 503 daemon_runtime_starting for the first second and the whole matrix is meaningless.

Seam 3 — CLI shim deletion. Export surface of packages/cli/src/serve/index.ts enumerated with the TypeScript compiler API on both arms: 120 symbols, identical, zero diff.

Seam 4 — Web Shell prompt admission. prompt-admission.test.ts 5/5. Full web-shell suite: head 4387 passed, base 4382 passed (+5 = the new file). 8 files fail identically on both arms in my link-farm environment (duplicate-React resolution and one un-built lib artifact) — same file set, same per-file counts, so not attributable to this PR.

Seam 5 — WebUI attachment lifecycle. Section 1.

Gates. Repo-wide npm run typecheck: 16 errors on the merge-base, the identical 16 on head (11 are the ajv import drift in @qwen-code/external-context-mem0, 3 are pre-existing web-shell errors, 2 are an unbuilt audio-capture dist in my tree). Worth restating because ci.yml does not run tsc. packages/cli serve suites touching this PR: 1405/1405.


Summary

seam verdict at 1422d79b22
1 · bounded ACP transport ✅ inbound preserved (0/200k), outbound relaxation monotone and sound
2 · trusted workspace routes ✅ 480 live probes per arm, 0 differences
3 · CLI shim deletion ✅ 120 exported symbols identical
4 · prompt admission ✅ faithful extraction, no duplicate definitions left behind
5 · attachment lifecycle ✅ preserved — and the R9-1 guard fixes a defect present on main
merge integrity ✅ all three main commits in PR-touched files preserved

Merging. Follow-ups, none blocking:

  1. Land the R9-1 fixture above (or your own equivalent) — right now the only thing standing between that guard and a future "this looks dead, delete it" is this comment.
  2. Name both live-bug fixes in the PR body: the MCP-tool-result transport kill, and this superseded-load resolution. Both are user-visible fixes filed as refactor.
  3. Still standing from last round: screenshots.spec.ts:778's ambiguous locator suppresses the visuals preview repo-wide.
  4. And the size point stands — next pass, land it staged.
中文版报告

维护者验证(第二轮)—— 在新 head 1422d79b22 上重建了环境

上一轮之后 head 发生了变化(5be8b5dd431422d79b22,一次与 main 的新合并),同时沙箱 /triage 报了一个未解决的 finding。所以我重建了两侧环境 —— PR head 1422d79b22 与其 merge-base 5c47cf95bc,各自在独立 worktree 中完整构建,Linux x86_64 / Node v22.22.2 —— 并重点做三件事:(1) 攻沙箱的 F1;(2) 检查这次 merge 是否悄悄丢掉了 main 落在本 PR 所改文件里的东西;(3) 在新的这对提交上把每个接缝的 A/B 重跑一遍,而不是沿用上一轮的数字。

结论:依然合入。而 F1 反倒是这个 PR 上最有价值的发现 —— 那个没有测试钉住的守卫不只是活代码,它修掉的是 main 上此刻就存在的一个 bug。下面给出 fixture,并做了三方向验证。

1. F1 结案 —— R9-1 守卫是承重代码,且 main 上存在它所防的缺陷

沙箱报告说:把 DaemonSessionProvider.tsx 中的 attemptedLoadForIteration 守卫回退掉,provider 套件依然全绿。在这个 head 上确认属实,而且范围更大:把守卫换成 true 后,整个 packages/webui 套件(36 个文件、654 个测试)仍然全绿。所以这是真实的覆盖缺口,不是沙箱假象。

随后我在真实 provider 的判定点埋了探针,跑完整套件,看为什么没有测试能杀掉它:

判定点处的状态 求值次数
pendingLoad 的 session ≠ 当前 session → 守卫无关 365
session 匹配,本轮迭代捕获的是同一个 load → 守卫放行 20
session 匹配,本轮迭代没有捕获 load → 注释中说明的创建流旁路 5
判别态 —— 捕获的 load ≠ 当前 pendingLoad 0 / 390

守卫存在的那个分支,现有测试从未进入过。于是我把进入该分支的 fixture 构造了出来。交错时序是:一次 restore 正在飞行中;后继者的 startPendingLoad 在 action 内部同步执行,而过期那次运行的续体是一个更早入队的 microtask —— 因此过期运行会越过自己的 abort 检查醒来,此时 pendingLoad 已经被替换。

守卫 结果
PR head 1422d79b22 存在 ✅ 通过
PR head,守卫改为 true 回退 expected 'resolved' to be undefined
merge-base 5c47cf95bc(即 main 没有 expected 'resolved' to be undefined

关键是第三行。这个 fixture 只驱动公开的 actions 接口(resumeSession),所以可以原样放到 main 上 —— 而它在 main失败mainpendingLoadToResolve 仅以 session 身份为键:

// main
const pendingLoadToResolve =
  pendingLoad?.sessionId === activeSession.sessionId ? pendingLoad : undefined;

同一 session 的两次重叠 restore 共享同一个 session 对象,因此被取代的那次运行会结算后继者的 pending load。用户可见的表现是:较新的那次 session 切换在自己的 load/resume 仍在飞行中时就报告完成 —— 调用方的 promise 结算在了一次并不属于它的 restore 上。

所以这是藏在重构里的第二处真实修复,与上一轮我指出的出站估算器那处并列。值得在 PR 正文中点名。

fixture 可直接粘进 DaemonSessionProvider.test.tsx(加入后 packages/webui 全套 655/655),见上文英文部分的折叠块。

2. 这次 merge 是干净的 —— 是查过的,不是假设的

自上一轮以来 main 在本分支下推进了 15 个提交,其中 3 个触及了本 PR 重写的文件。三个都完整保留了下来:

main 提交 head 上的状态
#9955 fix(core): expose resolved agent execution mode 在 —— isTaskExecutionMode 已接入 provider 的 subagent 投影
#9895 feat(daemon): support scoped workspace memory tasks 在 —— workspace-remember.ts 保留了 main 的版本
#9406 feat(serve): hide workspace Browse on headless daemon hosts 在 —— nativeDirectoryPickerSupported 保留在 App.tsx 中,与本 PR 对同一文件的抽取并存(相对 merge-base +59/−197)

merge 确实丢掉了一样东西,而且丢得正确:main 往本 PR 删除的兼容层 acp-session-bridge.ts 的 re-export 列表里加了 BridgeWorkspaceMemoryRememberTargetScope。该符号从来就不在公开的 serve/index.ts barrel 里,且 head 上 workspace-remember.ts 直接从 @qwen-code/acp-bridge/bridgeTypes 导入它。已无任何文件引用被删掉的那个文件。

另外也检查了这种形态的重构最容易出的问题:8 个被抽出的 admission helper,没有任何一个在 App.tsx 里残留重复定义。

3. 每个接缝都在新的这对提交上重新测量

接缝 1 —— 有界 ACP 传输。 transport-safety.ts 与我上一轮验证的 head 逐字节一致,估算器逻辑没有移动;但我还是在新的这对提交上重跑了差分。20 万条 wire 形态的值(生成后经 JSON.stringify/JSON.parse 往返),以真实 JSON.stringify 为 oracle,main 的两个估算器从 merge-base worktree 导出(该 worktree 的唯一 diff 是两个 export 关键字):0 拒绝、0 字节低估、与 main 入站估算器 0 判定差异,与 main 严格出站估算器有 58,310 处差异 —— 全部朝放行方向。acp-bridge:1812/1812

接缝 2 —— workspace-qualified 可信路由。 main 的新提交正落在这里,所以这一块是完整重做而不是沿用。两个真实 daemon(node dist/cli.js serve),开启 folder trust,每侧各注册一个 TRUST_FOLDER 和一个 DO_NOT_TRUST workspace,且两侧各用独立的 workspace 目录,避免带写操作的探测互相污染。80 条 workspace-qualified 路由 × 6 种选择子 = 每侧 480 次探测,状态码与错误码 0 差异。 63 条返回 403 untrusted_workspace;对未知或非绝对路径选择子,76 条返回 400 workspace_mismatch。我在测量前加了运行时就绪门 —— 没有它的话,daemon 头一秒会一律返回 503 daemon_runtime_starting,整个矩阵就毫无意义了。

接缝 3 —— CLI 兼容层删除。 用 TypeScript 编译器 API 在两侧枚举 packages/cli/src/serve/index.ts 的导出面:120 个符号,完全一致,零差异

接缝 4 —— Web Shell prompt admission。 prompt-admission.test.ts 5/5。web-shell 全套:head 4387 通过,base 4382 通过(+5 即新增文件)。有 8 个文件在两侧都以完全相同的方式失败(我这套 link-farm 环境的 duplicate-React 解析问题,以及一个未构建的 lib 产物)—— 相同文件集、相同计数,因此不归因于本 PR。

接缝 5 —— WebUI attachment lifecycle。 见第 1 节。

各项门禁。 全仓 npm run typecheckmerge-base 16 个错误,head 同样的 16 个(11 个是 @qwen-code/external-context-mem0ajv import 漂移,3 个是 web-shell 既有错误,2 个是我这棵树里 audio-capture 未构建的 dist)。之所以重申,是因为 ci.yml 并不跑 tscpackages/cli 中与本 PR 相关的 serve 套件:1405/1405

小结

接缝 1422d79b22 上的结论
1 · 有界 ACP 传输 ✅ 入站保持(0/20 万),出站放宽单调且可靠
2 · 可信 workspace 路由 ✅ 每侧 480 次实况探测,0 差异
3 · CLI 兼容层删除 ✅ 120 个导出符号完全一致
4 · prompt admission ✅ 忠实抽取,未残留重复定义
5 · attachment lifecycle ✅ 保持 —— 且 R9-1 守卫修掉了 main 上存在的一个缺陷
merge 完整性 ✅ 落在本 PR 所改文件里的 3 个 main 提交全部保留

合并。 后续项,均不阻塞:

  1. 把上面那个 R9-1 fixture(或你自己的等价实现)落进去 —— 目前挡在「这段看着像死代码,删掉吧」和那个守卫之间的,就只有这条评论。
  2. 在 PR 正文里点名两处真实修复:MCP 工具结果导致的传输被杀,以及这次的过期 load 结算。两者都是被归档为 refactor 的用户可见修复。
  3. 上一轮遗留仍然成立:screenshots.spec.ts:778 的歧义选择器会全仓性地压制视觉预览。
  4. 体量那点也仍然成立 —— 下一轮请分阶段落地。

🤖 Generated with Claude Code — Claude Opus 5 (1M context)

@DragonnZhang

Copy link
Copy Markdown
Collaborator Author

Thank you for the thorough round-2 verification — added the R9-1 pinning fixture as provided in ea7e945. Verified locally at the new head: the fixture passes with the attemptedLoadForIteration guard present, fails when the guard is reverted to session-identity-only adoption (expected 'resolved' to be undefined), and the full DaemonSessionProvider file runs 246/246 green with it. Will name the second live-bug fix (superseded restore settling the successor's pending load, present on main today) in the PR body.

中文说明

感谢第二轮的详尽验证——已在 ea7e945 中按提供的原样加入了 R9-1 钉住测试。在新头上本地验证:守卫存在时测试通过;把守卫还原为仅按 session 身份认领后测试失败(expected 'resolved' to be undefined);含该测试的 DaemonSessionProvider 全文件 246/246 全绿。会在 PR 描述中注明第二个活体 bug 修复(被取代的 restore 结算后继者的 pending load,该缺陷今天在 main 上存在)。

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 29, 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: 151 passed · 0 failed · 151 total

Flakiness gate: ⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (0 done) — no flakiness signal either way

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

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

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

抖动门:⚠️ timeout — the 15-minute budget elapsed before two full rounds completed (0 done) — no flakiness signal either way

Verification report

PR 9682 verification (follow-up round) — refactor: deepen architecture ownership boundaries

Verdict: merge-ready — 151/151 scripted assertions passed, 0 failed. Verified head:
ea7e9452e706468504e1dc0f979e69d721ac425f (base a88212572ee9f7f2107126eed7b4eec481a4437e).
The single previous-round finding (F1, a surviving mutation) is fixed: the new R9-1 pinning
fixture kills the exact mutant with the intended assertion. All other previous measurements were
re-run at the new head and reproduce.

中文 — 判定:✅ 通过(agent 判定)

本轮为跟进验证。上一轮唯一发现 F1(R9-1 守卫无测试钉住,突变存活)已被新提交
test(webui): add R9-1 pinning fixture from maintainer verification 修复:将该守卫条件替换为
true 的突变现在被新 fixture 以预期断言杀死(1 failed | 251 passed,失败断言为
expected 'resolved' to be undefined,即被取代的过期 restore 不得结算后继 pending load)。

  • A/B(重新测量): 传输安全集中化的全部单元在新 head 复现:27 个 wire 合法 payload 双侧全部放行(27/27 ↔ 27/27),head 估计对 JSON.stringify 字节数全部 sound 且纯字符串/容器类 13/13 逐字节相等;e9ffc4e 类(plain-data toJSON)base 严格估计器拒 5/5、head 放行 5/5;14 个危险形状 head 全拒(14/14),base 宽松估计器放行 11/14 且调用 4 次 accessor getter(head 为 0);逃逸密集 payload 的准入门槛 head 恰为 actual+2048(10050),base 宽松版在 6050 即放行(unsound)。共 126/126 断言。
  • 突变矩阵(全部重跑): M1–M5、W1–W3、P1–P2 全部被杀且归属与上一轮一致;上一轮存活的 guard → true 突变本轮被新 fixture 杀死。唯一补充观察:M4 的"成本变体"(boolean 成本 5→4)在单元测试中存活(arm 测试只钉"有界"不钉精确成本),但被本轮 A/B 的 soundness 单元(w17_false est≥actual)捕获——属测试粒度观察,非 PR 缺陷(实际成本 5 正确)。
  • 门禁(全部在新 head 重跑): acp-bridge 35 文件 1813 通过、webui daemon/session 11 文件 457 通过、web-shell 2 文件 582 通过 / 全量 212 文件 4611 通过、cli serve 定向 122 通过 + acp-http 61 通过、仓库 typecheck 退出 0。serve barrel 导出对比:52 值导出 + 68 类型导出与 base 完全一致。
  • 既往发现状态: F1 已修复;F2(预先存在的 NUL 字节,head 与 base 各 2 个)维持观察;F3(R6-1 钉住集为成对测试)经 W1/W2 重测维持原结论。
  • 未覆盖: 逐 commit 归因(depth-2 浅克隆,rev-list 为 1 而元数据列 28 个提交);上一轮 9 个 admission 级 dist 单元未单独重建(由 acp-bridge 全量套件含其中 9 个 admission 测试重新钉住);web-shell 对 base 的行为 A/B 与 resolveTrustedRuntime 的 HTTP 级 A/B(与上轮相同,无 delta,以套件覆盖);集成套件、Windows、UI E2E、SSE/daemon disposal(PR 自述范围外);抖动门为独立环节。

Previous-finding status

# finding severity status at the new head
F1 R9-1 attemptedLoadForIteration guard not pinned — guard → true survived the 248-test provider suite Suggestion fixed. Commit ea7e9452 adds the fixture "does not let a superseded restore settle the successor pending load". Re-measured: control green at 252 tests; the same mutant now dies at exactly that fixture, expected 'resolved' to be undefined (line 15200), 1 failed | 251 passed. The kill also proves reach — a scenario that never evaluates the guard could not flip outcome when it is replaced by true. Witness: 02-r9-1-fixture-kills-guard-mutant.png.
F2 two literal NUL bytes in DaemonSessionProvider.tsx (userBlockBoundaryKey), pre-existing observation stands. Re-counted at the new head: 2 NUL bytes; base (HEAD^1) also carries exactly 2. Deliberate collision-free separator preserved verbatim; file still classifies as binary to grep-like tools.
F3 R6-1 pinning set is a pair — only 1 of 3 successor lifecycle tests pins the association guard directly completeness stands, re-measured. W1 still kills exactly 2 tests (actions-level successor test + lifecycle late-release test), W2 kills the actions-level test; the characterization holds unchanged at the new head.

All previous measurements were re-run, not carried forward: the depth-2 checkout cannot reach
the previous round's head (5be8b5dd) or base (5ae363e2) to prove input closures byte-identical,
so the A/B, both mutation matrices, all gates, and the barrel comparison were re-executed against
the current tree. The only new PR content since the previous round is a main merge (conflict
resolution in the already-deleted acp-session-bridge.ts, plus dispatch.ts) and the R9-1 fixture.

Central claim + A/B (re-measured)

Central claim: the transport-safety centralization is behavior-preserving — wire-legal
inbound values keep their base admission behavior with byte parity to JSON.stringify, hazardous
shapes keep failing closed, and the e9ffc4e fix (plain-data toJSON admitted) is present.

Method: head arm = compiled packages/acp-bridge/dist/transport-safety.js; base arm = a HEAD^1
worktree whose only diff was two export keywords (estimateAcpHandlerBytes in bridge.ts,
estimatePreparedResponseBytes in spawnChannel.ts — verified by git diff --stat = 2 files,
+2/-2, quoted below in Methodology). The corpus was reconstructed for this round: 27 JSON-round-tripped
wire payloads + 14 constructed hazard shapes, driven through the shared ab-eval.mjs on both arms.
Witness: 01-ab-transport-safety-head-vs-base.png (raw: logs/ab-transport-safety.log).

cell oracle base head verdict
wire admit parity (27 payloads) estimator finite at generous limit 27/27 finite 27/27 finite parity holds
soundness (head ≥ JSON.stringify bytes) wire oracle n/a 27/27 holds (worst: w19_number_array +69)
exact byte parity (string/container only) head === actual n/a 13/13 holds
e9ffc4e class (plain-data toJSON, 5 shapes) strict-outbound refuses / head admits refuses 5/5 (regression class) admits 5/5 fix present
hazard refusal (14 shapes @​1024) sentinel === 1025 lenient admits 11/14 refuses 14/14 fail-closed preserved
accessor side effect getter call count lenient 4, strict 0 0 tightening justified
escape-heavy admission (actual=8002 B) min cap admitting 6050 (< actual+2048, unsound) 10050 (= actual+2048) safe tightening

Census deltas vs the previous round's table are corpus-shape effects (27 vs 24 payloads; lenient
admits 11/14 vs 12/14), not behavior changes — both arms were rebuilt from the same reconstructed
corpus this round. The previous round's 9 admission-level dist cells were not reconstructed
individually; admission behavior is re-pinned by the full acp-bridge gate (1813 tests incl. the 9
inbound handler admission tests in transport-safety.test.ts), all green at the new head.

Mutation matrix (all arms re-run at the new head)

Witness: 03-mutation-matrix-all-arms.png (raw logs: logs/ts-m*.log, logs/webui-w*.log,
logs/pa-p*.log, logs/provider-mutant-guard-true.log).

mutant suite result killed by
M0 control transport-safety.test.ts (18) GREEN
M1 revert e9ffc4e (blanket Object.hasOwn toJSON refusal) same RED 3/18 the 2 plain-data toJSON estimator tests + the admission test (sentinel vs parity bytes 76/31; admission rejects)
M2 drop callable-toJSON check same RED 1 "still refuses callable toJSON values…"
M3 drop accessor-toJSON check same RED 1 "still refuses accessor-backed toJSON values"
M4 delete boolean arm (fall-through → catch-all refusal) same RED 1 "estimates boolean and JSON null values on their dedicated arms" (expected 1025 to be less than 1025)
M5 drop frame-level record accessor check (positive control) same RED 1 "refuses accessor-backed members and indices at the frame level"
W0 control lifecycle+actions (119) GREEN
W1 drop load-association guard same RED 2 actions successor test + lifecycle late-release test (expected false to be true)
W2 actions stops passing pendingLoad same RED 1 actions successor test
W3 drop exemption release on reject (positive control) same RED 2 the reject/cancel exemption tests
P0 control prompt-admission.test.ts (5) GREEN
P1 drop after-admission branch same RED 1 "distinguishes rejection, unknown outcome, and post-admission failure"
P2 drop unknown branch same RED 1 same test
R9-1 guard → true (previous survivor) provider suites (252) RED 1 the new R9-1 fixture (expected 'resolved' to be undefined)

Note: an earlier M3/M4 formulation was void (wrong whitespace / equivalent mutant) and is excluded
from the counts; the corrected runs above are the evidence. The M4 cost variant (boolean cost
5→4, arm intact) survives the unit suite — the arm test asserts boundedness (toBeLessThan(1025)),
not the exact cost — but is caught by this round's A/B soundness cell (w17_false: estimate must be
≥ 5 = actual). Completeness observation only; the shipped cost is 5 and correct.

F1 fix detail

The fixture drives exactly the interleaving the previous report said would pin the guard: two
consecutive resumeSession('session-b') calls where the stale run's client.resume()
continuation is queued before the successor registers its own load, so the stale run reaches
the adoption point while attachmentLifecycle.pendingLoad is already the successor's. With the
guard intact, pendingLoadToResolve is undefined for the stale iteration; with guard → true
the stale iteration resolves the successor's promise, and secondOutcome flips from undefined
to 'resolved'. Test name ↔ fixture inputs match; the failing assertion names
expected-vs-actual; all other 251 tests stay green under the mutant.

Findings

No blocking findings. Non-blocking observations:

  1. (completeness) The boolean-arm unit test pins boundedness, not the exact cost — see the M4
    cost-variant note in the matrix table. A future cleanup touching estimateTransportValueBytes
    could change the boolean cost from 5 to 4 without any unit test going red; the wire-oracle
    soundness check (estimate ≥ JSON.stringify bytes) is what actually pins it.
  2. (pre-existing) F2's NUL bytes remain — intentional, unchanged, not attributable to this PR.

Not covered

  • Per-commit attribution: checkout is depth-2; git rev-list --count HEAD^1..HEAD^2 = 1 vs 28
    commits in the metadata snapshot. All evidence is against the aggregate HEAD^1..HEAD diff.
  • Previous round's 9 admission-level dist cells (adm1–adm9): not individually reconstructed;
    re-pinned via the full acp-bridge suite (their behavioral assertions live in
    transport-safety.test.ts's admission block, which was re-run green and mutation-checked by M1).
  • Web-shell prompt-admission A/B against base and resolveTrustedRuntime HTTP-level A/B:
    unchanged rationale from the previous round (no delta in these regions this round; covered by
    the re-run suites — web-shell 582/4611, cli serve targeted 122 + acp-http 61).
  • Integration suites, Windows/UNC paths, UI E2E, SSE projection, daemon runtime disposal —
    unchanged per the PR's own scope statement.
  • Flakiness gate — runs as a separate lane.
  • One harness fix occurred mid-round (the barrel-export parser initially dropped an export name
    into an in-braces comment fragment); the fixed parser was proven live by the fact that its
    buggy version detected a difference, and the corrected run reports identity.

Methodology

Environment: CI merge-ref checkout (HEAD = merge 5f7ec138, HEAD^1 = base tip
a8821257, HEAD^2 = PR head ea7e9452), node v22.23.2, builds pre-existing. The A/B head arm
imported the compiled dist/transport-safety.js; the base arm ran in a scratch worktree at
HEAD^1 whose verified diff was exactly two export keywords
(packages/acp-bridge/src/bridge.ts, packages/acp-bridge/src/spawnChannel.ts, +2/-2); the
worktree was removed after capture. Workspace-symlink confound: the PR touches no
package.json/package-lock.json (verified by empty git diff HEAD^1..HEAD --stat over all of
them), and the estimators import no @qwen-code/* workspace package, so sharing the root
node_modules is a clean control. Mutations were applied byte-exactly via python replace with
occurrence-count asserts and restored via git checkout after each run (working tree verified
clean at the end). Gates: acp-bridge 35 files/1813 tests, webui src/daemon/session 11 files/457,
web-shell 2 files/582 and full 212 files/4611, cli serve targeted 3 files/122 + acp-http 2
files/61, repo npm run typecheck exit 0, serve barrel export-set comparison 52 value + 68 type
exports identical base↔head. Harnesses, corpus, per-run logs, and matrix scripts live in this
artifact directory (ab-*.mjs, corpus.json, base-results.json, matrix-*.sh,
print-matrix.sh, barrel-compare.mjs, logs/); evidence PNGs in evidence/ were produced by
scripts/verify-capture.mjs.

Flakiness gate log

rounds=5 files=37 skipped=0
file packages/acp-bridge/src/transport-safety.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/transport-safety.test.ts
file packages/cli/src/serve/acp-http/dispatch-error.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/dispatch-error.test.ts
file packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/workspace-qualified-acp.test.ts
file packages/cli/src/serve/daemon-status.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/daemon-status.test.ts
file packages/cli/src/serve/fast-path.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/fast-path.test.ts
file packages/cli/src/serve/multi-workspace-sessions.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/multi-workspace-sessions.test.ts
file packages/cli/src/serve/routes/session-pr-backfill.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-pr-backfill.test.ts
file packages/cli/src/serve/routes/session-prompt-terminals.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-prompt-terminals.test.ts
file packages/cli/src/serve/routes/session-telemetry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/session-telemetry.test.ts
file packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-extensions-controller.test.ts
file packages/cli/src/serve/routes/workspace-git-branches.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-branches.test.ts
file packages/cli/src/serve/routes/workspace-git-diff.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-diff.test.ts
file packages/cli/src/serve/routes/workspace-git-log.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git-log.test.ts
file packages/cli/src/serve/routes/workspace-git.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-git.test.ts
file packages/cli/src/serve/routes/workspace-github-prs.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-github-prs.test.ts
file packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-qualified-extensions.test.ts
file packages/cli/src/serve/routes/workspace-setup-github.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-setup-github.test.ts
file packages/cli/src/serve/server-acp-child-extra-args.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server-acp-child-extra-args.test.ts
file packages/cli/src/serve/server-default-bridge-wiring.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server-default-bridge-wiring.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/cli/src/serve/server/device-flow-registry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/device-flow-registry.test.ts
file packages/cli/src/serve/server/session-archive.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server/session-archive.test.ts
file packages/cli/src/serve/session-id-admission.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/session-id-admission.test.ts
file packages/cli/src/serve/total-session-admission.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/total-session-admission.test.ts
file packages/cli/src/serve/workspace-agents.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-agents.test.ts
file packages/cli/src/serve/workspace-generation.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-generation.test.ts
file packages/cli/src/serve/workspace-git-state.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-git-state.test.ts
file packages/cli/src/serve/workspace-memory.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-memory.test.ts
file packages/cli/src/serve/workspace-qualified-rest.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-qualified-rest.test.ts
file packages/cli/src/serve/workspace-registry.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-registry.test.ts
file packages/cli/src/serve/workspace-remember.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-remember.test.ts
file packages/cli/src/serve/workspace-route-runtime.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-route-runtime.test.ts
file packages/cli/src/serve/workspace-service/__tests__/integration.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/workspace-service/__tests__/integration.test.ts
file packages/web-shell/client/prompt-admission.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/prompt-admission.test.ts
file packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/DaemonSessionProvider.test.tsx
file packages/webui/src/daemon/session/actions.test.ts: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/actions.test.ts
file packages/webui/src/daemon/session/attachment-lifecycle.test.ts: (cd packages/webui) npx --no-install vitest run ./src/daemon/session/attachment-lifecycle.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/acp-bridge/src/transport-safety.test.ts: P
  packages/cli/src/serve/acp-http/dispatch-error.test.ts: P
  packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: P
  packages/cli/src/serve/daemon-status.test.ts: P
  packages/cli/src/serve/fast-path.test.ts: P
  packages/cli/src/serve/multi-workspace-sessions.test.ts: P
  packages/cli/src/serve/routes/session-pr-backfill.test.ts: P
  packages/cli/src/serve/routes/session-prompt-terminals.test.ts: P
  packages/cli/src/serve/routes/session-telemetry.test.ts: P
  packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: P
  packages/cli/src/serve/routes/workspace-git-branches.test.ts: P
  packages/cli/src/serve/routes/workspace-git-diff.test.ts: P
  packages/cli/src/serve/routes/workspace-git-log.test.ts: P
  packages/cli/src/serve/routes/workspace-git.test.ts: P
  packages/cli/src/serve/routes/workspace-github-prs.test.ts: P
  packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: P
  packages/cli/src/serve/routes/workspace-setup-github.test.ts: P
  packages/cli/src/serve/server-acp-child-extra-args.test.ts: P
  packages/cli/src/serve/server-default-bridge-wiring.test.ts: P
  packages/cli/src/serve/server.test.ts: P
  packages/cli/src/serve/server/device-flow-registry.test.ts: P
  packages/cli/src/serve/server/session-archive.test.ts: P
  packages/cli/src/serve/session-id-admission.test.ts: P
  packages/cli/src/serve/total-session-admission.test.ts: P
  packages/cli/src/serve/workspace-agents.test.ts: P
  packages/cli/src/serve/workspace-generation.test.ts: P
  packages/cli/src/serve/workspace-git-state.test.ts: P
  packages/cli/src/serve/workspace-memory.test.ts: P
  packages/cli/src/serve/workspace-qualified-rest.test.ts: 
  packages/cli/src/serve/workspace-registry.test.ts: 
  packages/cli/src/serve/workspace-remember.test.ts: 
  packages/cli/src/serve/workspace-route-runtime.test.ts: 
  packages/cli/src/serve/workspace-service/__tests__/integration.test.ts: 
  packages/web-shell/client/prompt-admission.test.ts: 
  packages/webui/src/daemon/session/DaemonSessionProvider.test.tsx: 
  packages/webui/src/daemon/session/actions.test.ts: 
  packages/webui/src/daemon/session/attachment-lifecycle.test.ts: 

verdict: timeout
summary: the 15-minute budget elapsed before two full rounds completed (0 done) — no flakiness signal either way

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/transport-safety.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/dispatch-error.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/workspace-qualified-acp.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/daemon-status.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/fast-path.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/multi-workspace-sessions.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-pr-backfill.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-prompt-terminals.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/session-telemetry.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-extensions-controller.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-branches.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-diff.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git-log.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-git.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-github-prs.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-qualified-extensions.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-setup-github.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server-acp-child-extra-args.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server-default-bridge-wiring.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/device-flow-registry.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server/session-archive.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/session-id-admission.test.ts: P (exit 0)
round 1 · package

...truncated -- full content in the run artifacts.

Evidence images

01-ab-transport-safety-head-vs-base

02-r9-1-fixture-kills-guard-mutant

03-mutation-matrix-all-arms

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 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.

Partially reviewed — gaps disclosed.

12 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R13-1 watchdog release-before-callback ordering unpinned (attachment-lifecycle.test.ts:96) — already reported as R12-2 and round-2/3/5/6/7/8 convergence-posture deferrals (reviews 5013080184, 5014440443, 5029145840, 5032096362, 5045295751, …
  • admission-budget release on handler-exception path unpinned (transport-safety.test.ts:254) — already reported as round-6/7 convergence-posture deferrals (reviews 5032096362, 5045295751) and R8-9
  • error-keyed releaseMessage branch unpinned (transport-safety.test.ts:394) — already reported as round-5/7 convergence-posture deferrals (reviews 5029145840, 5045295751) and R8-13
  • object-identity-keyed release path unpinned (transport-safety.test.ts:394) — already reported as the round-9 deferral (review 5054370521, runRequest reservation identity)
  • log-safe mask errorKind/status/hint preservation contract unpinned (transport-safety.ts:491) — already reported as the round-9 deferral (review 5054370521)
  • phantom acp-session-bridge denylist entry (scripts/check-serve-fast-path-bundle.js:84) — already reported as the round-5 deferral (review 5029145840)
  • estimator number arm and boolean-false unpinned (transport-safety.test.ts:50) — already reported as the round-7 deferral (review 5045295751) and R8-8
  • record-cycle seen-check mutant survives (transport-safety.test.ts:152) — already reported as the round-3 deferral (review 5014440443) and R8-12
  • throwing onTimeout / load-always-settles guarantee unpinned (attachment-lifecycle.ts:72) — already reported as the round-6 deferral (review 5032096362), R8-3 and R12-4
  • retained-budget count arm unpinned (transport-safety.ts:86) — already reported as the round-12 deferral (review 5057370479)
  • R9-1 provider-layer release-guard wiring unpinned (DaemonSessionProvider.tsx:1619) — already reported as the round-10 deferral (review 5055463393) and the R11-1/R12-1 lineage
  • watchdog clearTimeout disarm unpinned (attachment-lifecycle.ts:106) — already reported as the round-8 deferral (review 5051873342)

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 3.

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

Mechanism health: this round did not close cleanly, so it withholds the incremental anchor — and the round it recovered had no anchor this round could use either — none at all, one with no certifier, one certified by an identity other than the one this round runs under, or one this round's fetch refused or resolved to the head — so the next review re-reads the whole diff unless recovery grafts an earlier own anchor that the round running it can use onto the complete work list this round leaves behind, and keeps doing so until a round's marker carries an anchor again or a graft lands that the round running it can use. (Stated, not acted on — this changes nothing about what the round posts.)

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 12 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:reverse audit — did not converge within the reverse-audit round cap of 3。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

机制健康:本轮未能干净收尾,因而扣留了增量锚点,而它恢复到的那一轮也没有留下本轮可用的锚点——要么完全没有、要么没有认证者、要么由本轮运行身份之外的身份认证、要么被本轮的获取拒绝或解析为头提交——因此下一次评审将重读整个 diff,除非恢复流程把本轮能使用的更早自有锚点嫁接到本轮留下的完整工作清单上;并会一直如此,直到某一轮的标记重新带上锚点,或落地的嫁接能被运行该轮的评审使用。(仅陈述,不据此行动——这不改变本轮发布的任何内容。)

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

@DragonnZhang

Copy link
Copy Markdown
Collaborator Author

Merge-conflict status: this branch currently conflicts with main, which has since merged #10418 (explicit daemon session contexts) plus #10268 (cancel timed-out session initialization). Both substantially rewrite the session-context / pending-load machinery that this PR refactors into SessionAttachmentLifecycle, so reconciling them is delicate.

I attempted the merge and resolved the textual conflicts, but the standalone-session suite (resumes standalone sessions through the dedicated route, keeps an explicit standalone context through detached create and attach, returns to the workspace prop after clearing a standalone restore) timed out — the standalone flow from #10418 was not fully wired against the lifecycle architecture. Rather than push a merge that may regress session concurrency, I reverted it. This conflict needs a careful reconciliation of #10418's session-context handling with the lifecycle refactor, ideally with the full test suite runnable.

中文说明

合并冲突状态:本分支当前与 main 冲突,main 此后合入了 #10418(显式守护进程会话上下文)与 #10268(取消超时的会话初始化)。两者都大幅重写了会话上下文/待加载(pending-load)机制,而本 PR 正是把这套机制重构进 SessionAttachmentLifecycle,因此调和二者需要谨慎处理。我已尝试合并并解决了文本冲突,但 standalone 会话套件(resumes standalone sessions through the dedicated routekeeps an explicit standalone context through detached create and attachreturns to the workspace prop after clearing a standalone restore)超时——#10418 的 standalone 流程未能与生命周期架构完全接通。为避免推送可能回归会话并发的合并,我已回退。该冲突需要把 #10418 的会话上下文处理与生命周期重构仔细调和,最好能跑完整测试套件。

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.

3 participants