feat(daemon): Profile ACP channel initialization - #7145
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Thanks for the PR, @doudouOUC! Template looks good ✓ Problem: This is a real observability gap, not theoretical hardening. The daemon's Direction: Aligned. Startup profiling and performance observability are core to daemon health. The opt-in negotiation via ACP metadata is the right approach — no overhead when unrequested, backward-compatible in both directions. The design doc under Size: ~877 production logic lines, ~503 test lines, ~104 docs lines. Not a refactor (title is Approach: The scope feels right for what it claims — a fixed, bounded profiler with ~20 marks, a simple state machine, and fail-open semantics. The dynamic import in Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 这是一个真实的可观测性缺口,而非理论性加固。daemon 的 方向: 对齐。启动 profiling 和性能可观测性是 daemon 健康的核心。通过 ACP metadata 进行可选协商是正确方案——未请求时零开销,双向兼容。 规模: 约 877 行生产代码、约 503 行测试、约 104 行文档。非 refactor(标题为 方案: 范围合理——固定有界的 profiler(约 20 个 mark),简单状态机,fail-open 语义。 进入代码审查 🔍 — Qwen Code · qwen3.7-max Reviewed at |
Code Review + Test ResultsIndependent proposal (before reading the diff): I would have added a simple mark-based profiler gated on ACP route detection, dynamically imported to avoid non-ACP overhead. Protocol negotiation via Comparison: The PR matches this approach closely. The profiler is a clean state machine ( No critical issues found. A few observations:
Build: ✅ Typecheck: ✅ Tests:
Real-Scenario TestingThis PR adds internal profiling infrastructure with no user-visible behavior change, so the tmux test verifies the non-ACP path is unaffected. Non-ACP path: version command and interactive prompt both work correctly. The profiling module is not loaded (confirmed by the bundle guard test keeping 中文说明代码审查 + 测试结果独立方案(读 diff 前): 基于 ACP 路由检测的 mark-based profiler,动态 import 避免非 ACP 路径开销,通过 对比: PR 方案与独立方案高度一致。Profiler 是干净的状态机,父端校验正确拒绝 NaN/Infinity/负值/超限值,bridge 用 try/catch 包裹 enrichment, 无 critical 问题。 构建 ✅,类型检查 ✅,全部 764 个聚焦测试通过 ✅。 非 ACP 路径的 tmux 测试确认 CLI 正常工作,profiling 模块未被加载(bundle guard 测试也确认了这一点)。 — Qwen Code · qwen3.7-max Reviewed at |
|
Confidence: 4/5 — Clean implementation of focused observability infrastructure. No correctness issues, well-tested, fail-open throughout. The PR does exactly what it says: adds a bounded, opt-in startup profiler for ACP channel initialization. The profiler is a simple state machine with no I/O beyond The test coverage is thorough: malformed profiles, missing profiles, unsupported versions, telemetry failures, Config phase ordering in safe mode, and the serve fast-path bundle boundary. The tmux sanity check confirms the non-ACP path is unaffected. The one mild reservation: the Approving. ✅ 中文说明置信度:4/5 — 聚焦的可观测性基础设施,实现干净。无正确性问题,测试充分,全程 fail-open。 PR 完成了它所声称的:为 ACP channel initialize 添加有界的、可选的启动 profiler。Profiler 是简单状态机,无 I/O(仅 唯一轻微顾虑: 批准合并 ✅ — Qwen Code · qwen3.7-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM — well-isolated profiling instrumentation with thorough fail-open design. Maintainer should confirm the telemetry path addition before merging. ✅
Review: feat(daemon): Profile ACP channel initializationOverviewOpt-in ACP What I verified (against the actual code, not just the diff)
No correctness blockers found. The findings below are minor. Findings
Test coverageGood and targeted: parser validation matrix (NaN/Infinity/negative/over-cap/unknown-version/extra-fields), freeze + first-mark-wins semantics, bootstrap-window gating, negotiation on/off (including VerdictLGTM with minor suggestions. Well-scoped observability change: bounded fixed-name payload, fail-open on every path, no behavior change to readiness/ordering/timeouts, and the module-boundary protections are enforced by tests rather than convention. Findings 1–5 are non-blocking; none require a re-review if addressed. 中文版本评审:feat(daemon): Profile ACP channel initialization概述可选协商的 ACP 已核实的关键点(对照实际代码而非仅 diff)
未发现阻塞性正确性问题,以下均为次要发现。 发现
测试覆盖覆盖到位:解析校验矩阵、冻结与首 mark 语义、bootstrap 窗口门控、协商开/关(含 结论LGTM,附少量建议。 范围克制的可观测性改动:payload 固定有界、全路径 fail-open、不改变 readiness/顺序/超时语义,模块边界由测试而非约定保障。发现 1–5 均不阻塞合并。 |
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
@wenshao Thanks for the detailed review. I handled the non-blocking findings as follows:
No inline review threads were open, so there was nothing to resolve. The documentation-only follow-up passed Prettier and diff checks. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: reverse audit — its prompt was built, but no agent was launched with it that opened its brief, so the reverse-audit pass did not run.
— qwen3.7-max via Qwen Code /review
| it('returns the startup profile only when initialize metadata requests v1', async () => { | ||
| initializeAcpStartupProfiler(); |
There was a problem hiding this comment.
[Suggestion] The 'QwenAgent MCP SSE/HTTP support' describe block's beforeEach does not call resetAcpStartupProfilerForTesting(), so the profiler module state (enabled=true, frozen=true, marks populated) leaks from this test into subsequent tests in the same file. The 'runAcpAgent shutdown cleanup' block already resets correctly in its beforeEach/afterEach.
Failure scenario: a future test added to this or a subsequent describe block that asserts response does not have _meta would get a stale frozen profile attached unexpectedly.
| it('returns the startup profile only when initialize metadata requests v1', async () => { | |
| initializeAcpStartupProfiler(); | |
| it('returns the startup profile only when initialize metadata requests v1', async () => { | |
| resetAcpStartupProfilerForTesting(); | |
| initializeAcpStartupProfiler(); |
— qwen3.7-max via Qwen Code /review
Re-review (round 2): delta since 34882f3Verified the follow-up commit
VerdictLGTM — ready to merge. No code changed since the reviewed head; the docs delta is correct and complete. The round-1 process note stands: 中文版本复审(第二轮):自 34882f3 以来的增量已对照评审发现核实了后续提交
结论LGTM,可以合并。 自上次评审的 head 以来代码零改动,文档增量正确且完整。第一轮的流程提示仍然有效: |
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Local build & real-run verification (merge reference)Follow-up to my review rounds (round 1, round 2 LGTM) — this round is build + real-execution evidence at the same head Verdict: works as designed on every axis I could drive — ready to merge from my side. Arm A — real
|
| # | request _meta |
result |
|---|---|---|
| 1 | {v:1} opt-in |
✅ profile returned: complete:true, 11 phases + 9 config durations, all finite/2-dp/≤600s, Σ(phases) = processToResponseMs exactly (drift ≈ 0) — the partition invariant holds on the wire |
| 2 | none | ✅ initialize OK, no profile key (opt-in honored) |
| 3 | {v:2} unknown version |
✅ initialize OK, no profile (version-gated) |
| 4 | {v:1}, initialize ×2 |
✅ second response returns the byte-identical frozen profile |
No paths, extension names, or user-derived values anywhere in the payload — fixed schema only, matching the design doc's privacy claim.
Cold vs warm run of the same build is exactly the attribution this PR exists to surface: first-ever run processToResponseMs=2217.96 with ripgrepProbeMs=737.43 dominating toolRegistryMs=743.42; warm runs drop to single-digit-ms ripgrepProbeMs.
Arm B — backward compatibility (new parent → old child)
Merge-base build (verified feature-absent: 0 channelStartupProfile hits in dist, no acp-startup-profiler.js) receives the opt-in _meta and ignores it gracefully: initialize succeeds, no profile. Old children are safe behind a new daemon.
Arm C — real qwen serve daemon, span enrichment A/B
Launched the real daemon (serve --port 0 --workspace …) with telemetry {enabled, target:local, outfile}, POST /session → daemon spawns a real ACP channel child → inspected the qwen-code.daemon.bridge / channel.initialize span in the outfile:
- PR daemon: 24
qwen-code.daemon.acp_startup.*attributes (2 profile + 2 child + 10 phase + 9 config + 1 transport),profile.complete=true,response_transport_ms≈0.7ms(sane for a local pipe).unattributedMscorrectly appears only aschild.unattributed_ms(not duplicated in thephase.*group). - merge-base daemon: same span, same operation, 0
acp_startupattributes — purely additive enrichment.
Suites & gates
- Focused suites at PR head (fresh worktree): acp-bridge 414/414 (incl. new
channel-startup-profile.test.ts), core 27/27 (config.safe-modeordering +daemon-tracing), cli 307/307 (acp-startup-profiler+cliboundary +acpAgent), scripts 16/16 — Σ 764 passed, 0 failed. npm run check:serve-fast-path-bundle(clean → cli-only build → DEV bundle → boundary check): passed —Serve fast-path bundle closure check passed., exit 0 — the new forbidden-input rules (Gemini runtime / ACP agent runtime / ACP startup profiler must stay out of the serve pre-listen graph) hold on a real bundle.- CI at head: Ubuntu Node 22.x full suite green (31m); macOS/Windows legs classifier-skipped.
Notes (non-blocking, unchanged from round 2)
The five minor findings from round 1 stand as accepted/documented (raw-argv space-form --acp detection, settings-driven Zed ACP not profiled, ~1–2 KB negotiation with telemetry off, frozen profile on repeat initialize — confirmed live in Arm A #4, unattributedMs includes request-arrival wait — documented in the design doc). None affect correctness of what this PR ships.
🇨🇳 中文版报告(点击展开)
本地构建 + 真实运行验证(合并参考)
继两轮 review(round 1、round 2 LGTM)之后,本轮为同一 head b3db6d8ad 的构建 + 真实执行证据。所有验证均在隔离 worktree 中对全新构建产物(npm ci + 完整 build)进行,并与 merge-base 7795a1b6e 做 A/B 对比。
结论:各条路径均按设计工作,我这边认为可以合并。
Arm A — 真实 --acp 子进程、raw JSON-RPC(无 mock)
以隔离 HOME + QWEN_CODE_NO_RELAUNCH=true(对齐 spawnChannel.ts)启动构建产物,通过 stdio ndjson 驱动 initialize:
| # | 请求 _meta |
结果 |
|---|---|---|
| 1 | {v:1} 协商 |
✅ 返回剖面:complete:true,11 个 phase + 9 个 config 耗时,全部有限/两位小数/≤600s,Σ(phases) 与 processToResponseMs 完全相等(漂移≈0)——分区不变量在线上成立 |
| 2 | 无 | ✅ initialize 正常,响应无剖面键(opt-in 生效) |
| 3 | {v:2} 未知版本 |
✅ initialize 正常,无剖面(版本门控) |
| 4 | {v:1},initialize 两次 |
✅ 第二次返回完全相同的冻结剖面 |
载荷中无任何路径、扩展名或用户派生值——只有固定字段,与设计文档的隐私声明一致。
冷/暖对比正是该 PR 要暴露的归因:首次运行 processToResponseMs=2217.96,其中 ripgrepProbeMs=737.43 占 toolRegistryMs=743.42 的绝大部分;暖机后 ripgrepProbeMs 降至个位数毫秒。
Arm B — 向后兼容(新 parent → 旧 child)
merge-base 构建(已确认无该特性:dist 中 0 处 channelStartupProfile、无 acp-startup-profiler.js)收到 opt-in _meta 后优雅忽略:initialize 成功、无剖面。旧 child 在新 daemon 后面是安全的。
Arm C — 真实 qwen serve daemon 的 span 增强 A/B
以 telemetry {enabled, target:local, outfile} 启动真实 daemon,POST /session 触发真实 ACP channel 子进程 spawn,检查 outfile 中 qwen-code.daemon.bridge / channel.initialize span:
- PR daemon:24 个
qwen-code.daemon.acp_startup.*属性(2 profile + 2 child + 10 phase + 9 config + 1 transport),profile.complete=true,response_transport_ms≈0.7ms(本地管道,合理)。unattributedMs仅出现在child.unattributed_ms,未在phase.*组重复。 - merge-base daemon:同一 span、同一操作,0 个
acp_startup属性——纯增量增强。
测试与门禁
- PR head 聚焦套件(全新 worktree):acp-bridge 414/414(含新增
channel-startup-profile.test.ts)、core 27/27、cli 307/307、scripts 16/16 —— 共 764 通过,0 失败。 npm run check:serve-fast-path-bundle(clean → cli-only build → DEV bundle → 边界检查):通过 ——Serve fast-path bundle closure check passed.,exit 0 —— 新增的禁止项(Gemini runtime / ACP agent runtime / ACP startup profiler 不得进入 serve pre-listen 图)在真实 bundle 上成立。- head 的 CI:Ubuntu Node 22.x 全量套件绿(31 分钟);macOS/Windows 由分类器跳过。
备注(非阻塞,与 round 2 一致)
Round 1 的五个次要发现维持已接受/已文档化状态(raw-argv 空格形式 --acp 检测、settings 驱动的 Zed ACP 不产生剖面、telemetry 关闭时仍有 ~1–2 KB 协商开销、重复 initialize 返回冻结剖面——已在 Arm A #4 实测确认、unattributedMs 含请求到达等待——设计文档已说明)。均不影响本 PR 交付内容的正确性。
Verification environment: macOS (darwin 24.6), Node v22, isolated worktrees + fresh npm ci; probes drive built artifacts only (no source-mode shortcuts). Evidence scripts: raw-ndjson ACP initialize probe + live serve daemon span probe with telemetry outfile.
Resolves the ACP bootstrap conflict (keep #7145 profiling wrapper + warning emission), drops the session.ts warnings block superseded by #7174, and guards the ACP getWarnings call against stubbed configs. Claude-Session: https://claude.ai/code/session_01KwsYFzWZ6VLCxVN8MbeFXb




What this PR does
This PR adds an opt-in ACP initialize metadata handshake that records a fixed, bounded set of child-process startup phases and attaches validated timings to the existing parent
channel.initializespan. It introduces a lightweight ACP-only profiler before the Gemini import, instruments bootstrap configuration and transport/handler stages, validates profiles fail-open, and protects theqwen servepre-listen bundle from statically loading the ACP/Gemini profiling runtime.Why it's needed
Cold daemon channel startup currently spends roughly one second inside an opaque initialize boundary. The existing span cannot distinguish process and module loading, configuration, tools, or response transport, so selecting a safe optimization is guesswork. This change provides attributable measurements without changing readiness, initialization ordering, timeouts, cleanup, retry, concurrent preheat, or Session semantics.
Reviewer Test Plan
How to verify
Build the CLI bundle and send an ACP initialize request with
_meta.qwen.daemon.channelStartupProfile.v = 1; confirm the response contains a bounded v1 profile with the fixed phase and configuration keys and that the parent initialize span receives only validated fixed-name attributes. Repeat without the metadata and confirm no profile is returned. Run the malformed/legacy compatibility tests and the serve fast-path closure check; initialize must still succeed when profiling or telemetry data is missing, malformed, unsupported, or throws.Evidence (Before & After)
Before:
channel.initializeexposes one aggregate duration and cannot attribute child startup cost.After: negotiated initialize responses expose fixed, bounded phase timings and enrich the existing parent span; unnegotiated responses and Session behavior are unchanged. Focused validation passed for the ACP bridge (414 tests), CLI profiling/entry/agent paths (356 tests), core configuration/tracing paths (27 tests), and bundle guard (16 tests).
npm run build,npm run typecheck,npm run lint:ci, andnpm run check:serve-fast-path-bundlealso passed on the rebased branch.Tested on
Environment (optional)
macOS local source and release bundle with Node.js 24; negotiated, unnegotiated, malformed, telemetry-disabled, safe-mode, and legacy compatibility paths were covered by focused tests.
Risk & Scope
Linked Issues
N/A
中文说明
本 PR 的改动
本 PR 增加了一个可选协商的 ACP initialize 元数据握手:在子进程中采集一组固定且有界的启动阶段耗时,并将经过校验的耗时写入父进程现有的
channel.initializespan。它在导入 Gemini 前启用轻量的 ACP 专用 profiler,对 bootstrap 配置、transport 和 initialize handler 阶段进行观测,以 fail-open 方式校验 profile,同时防止qwen serve的 pre-listen bundle 静态加载 ACP/Gemini profiling runtime。为什么需要
冷启动 daemon channel 目前约有一秒耗时位于不可拆分的 initialize 边界内。现有 span 无法区分进程和模块加载、配置、工具以及响应传输耗时,因此缺少证据来选择安全的优化项。本改动提供可归因的数据,同时不改变 readiness、初始化顺序、超时、清理、重试、并发预热或 Session 语义。
Reviewer 测试计划
如何验证
构建 CLI bundle,并发送带
_meta.qwen.daemon.channelStartupProfile.v = 1的 ACP initialize 请求;确认响应包含字段固定且有界的 v1 profile,且父进程 initialize span 只接收经过校验、名称固定的属性。去掉该元数据后重复请求,确认响应不包含 profile。运行畸形数据/旧版本兼容测试和 serve fast-path 闭包检查;当 profiling 或 telemetry 数据缺失、畸形、版本不受支持或抛出异常时,initialize 仍必须成功。证据(改动前后)
改动前:
channel.initialize只暴露一个总耗时,无法归因子进程启动成本。改动后:协商过的 initialize 响应会暴露固定且有界的阶段耗时,并丰富父进程现有 span;未协商响应和 Session 行为保持不变。ACP bridge(414 个测试)、CLI profiling/入口/agent 路径(356 个测试)、core 配置/tracing 路径(27 个测试)以及 bundle guard(16 个测试)的聚焦验证均通过;rebase 后的分支还通过了
npm run build、npm run typecheck、npm run lint:ci和npm run check:serve-fast-path-bundle。已测试系统
环境(可选)
macOS 本地源码和 release bundle,Node.js 24;聚焦测试覆盖了协商、未协商、畸形数据、telemetry disabled、safe mode 和旧版本兼容路径。
风险与范围
关联 Issue
N/A