refactor(dingtalk): remove obsolete background response aggregation - #11570
Conversation
Offline behavior verification reportVerified commit: Published baseline: qwen 0.23.2. With aggregation enabled, Agent output was withheld until the terminal marker, and session retirement could flush a partial result. With the setting absent or false, output was immediate. Built-adapter verification: Node.js 22.17.0 on macOS, isolated worktree, intercepted final transports. The eight combinations of omitted/false/true/string legacy values and group/DM targets all produce the same sequence:
Two harness runs passed, checking 54 intercepted messages total. Agent labels and Shell text were preserved; group delivery selected the proactive transport and DM selected the reply transport. Named-session labels and file markers remained intact at those seams. The retired catalog field and checked aggregation state/methods are absent; no output-mode configuration or capability was added. Local checks passed:
Limitations: the harness stops at the final transport seam. It does not verify live DingTalk delivery/rendering, file uploads or production behavior. No credentials, real DingTalk sends or deployment were used. Hosted CI is separate from these local results. 中文验证报告验证提交: 已发布 qwen 0.23.2 基线:启用聚合时,Agent 输出会等待终态后合并发送,会话退出可能补发部分结果;未配置或设为 false 时即时发送。 在 macOS、Node.js 22.17.0、独立 worktree 中对最终构建适配器拦截传输验证。旧键省略、false、true、字符串四种值与群聊/私聊组合成八种场景,全部表现一致:第一条 Agent 响应立即形成一条消息;交错的 Shell 响应及第二条 Agent 响应使消息数在终态前达到三条;空终态、会话退出和频道断开均不增加消息。 两遍 harness 共核验 54 条拦截消息。Agent 标签和 Shell 原文保留;群聊走主动投递,私聊走回复;命名会话标签和文件标记保留到传输接口。旧管理字段与检查到的聚合状态/方法均已移除,也未增加输出模式字段或能力。 本地钉钉 598 个、共享频道基类 692 个、CLI 配置/管理 135 个测试,共 1,425 个测试通过。全仓构建、类型检查、打包、改动文件 ESLint、Prettier 和差异检查通过;两轮自审与一次独立只读评审未发现问题。 限制:harness 的证据止于最终传输接口,不证明真实钉钉投递/渲染、文件上传或生产行为。未使用凭证、发送真实钉钉消息或部署。远端 CI 与上述本地结果独立。 |
|
Thanks for the PR! Template looks good ✓ — every required heading is there, with a matching Chinese section. Problem: this is a deletion, not a fix, so there is no reproduction to ask for. The justification is that the aggregation path is obsolete and that #11561 needs it gone first — and #11561 does say so explicitly ("isolated in #11570, which should land first"), so there is a named downstream consumer rather than a hypothetical one. What the description does not carry is evidence that anyone still runs with the setting enabled. That is fine for a cleanup, but it is the reason the migration note matters more than usual here. Direction: aligned in principle — one delivery behaviour instead of two, and the buffering path was genuinely intricate. One thing worth saying out loud: this removes a documented, user-facing Size: the core-module gate does not apply — nothing here matches the core paths ( Approach: the scope feels right, and I checked whether it was actually minimal rather than taking the description's word for it. Everything deleted traces back to the aggregation feature: the Risk: no elevated risk signals — none of the changed files match the revert-correlated paths. The real risk is the product one above, not a code path. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 所有必需小标题齐全,中文说明也对应完整。 问题: 这是一个删除类改动,不是 bug 修复,所以没有"复现"可要求。它的理由是聚合路径已经过时,且 #11561 需要先移除它 —— #11561 里确实明确写了("isolated in #11570, which should land first"),因此存在一个明确的下游依赖方,而不是假想需求。描述中没有的是"当前是否还有用户启用该设置"的证据。对清理类改动来说这可以接受,但也正因为如此,这里的迁移说明比平时更重要。 方向: 原则上是对齐的 —— 用一种投递行为取代两种,而缓存路径本身确实非常复杂。但有一点需要明确说出来:这移除的是一个有文档、面向用户的 规模: 核心模块 gate 不适用 —— 这里没有任何文件命中核心路径( 方案: 范围是合理的,我也实际核查了它是否真的最小,而不是只采信描述。所有被删的内容都能追溯到聚合功能本身: 风险: 无升级风险信号 —— 改动文件都没有命中与 revert 相关的路径。真正的风险是上面提到的产品层面风险,而不是某条代码路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewNo critical blockers. This is a clean deletion, and I checked the seams rather than trusting the diff shape. The thing that makes a 1,000-line removal reviewable is whether anything still points at what went away. I grepped the head revision for all thirty removed symbols — the aggregation maps and detached sets, the drain/flush/schedule/complete/create/remove/refresh helpers, the pending-terminal structures, both delivery-error classes,
The rewritten dispatch is semantically identical to the old non-aggregating branch: empty text falls through to the base, which returns early on an empty trim; non-empty text gets the same I also looked for scope creep, since a diff this size is where it hides. There is none. Five tests that read as "added" are byte-identical relocations past the deleted blocks — I compared the removed and added line multisets, and only 22 lines of test content are genuinely new. Those 22 are one replacement test pinning the source label on a DM background reply, and a mock-harness fix so One non-blocking observation, worth a maintainer's eye because it touches the migration note. The description says a leftover Skipped the sequence diagram and the changed-files table — a five-file pure deletion whose shape is already described above does not need either. Test evidenceEvidence carried here is the PR's own CI, read through the API. This is an unattended run, so I did not build or execute anything from this PR; per the same rule there is no tmux capture (N/A — unattended CI, and there is no TUI surface here regardless). Zero checks are red on this commit. Two things make the green lanes mean something here. The install step runs the repo's prepare script, which runs the full workspace build, so a green The honest gap is platform coverage: the macOS and Windows Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Sandboxed verification would settle this: 中文说明代码审查没有发现阻塞性问题。这是一次干净的删除,我核查的是各个接缝,而不是只看 diff 的形状。 一千行的删除之所以可评审,关键在于是否还有东西指向被删掉的内容。我在 head 版本上检索了全部三十个被移除的符号 —— 聚合 Map 与 detached 集合、drain/flush/schedule/complete/create/remove/refresh 系列辅助方法、pending-terminal 结构、两个投递错误类、
重写后的 dispatch 与旧的非聚合分支在语义上完全一致:空文本回落到基类,基类在 trim 为空时提前返回;非空文本走同样的 我也专门找了范围蔓延,这种体量的 diff 最容易藏。结论是没有。看起来像"新增"的五个测试,其实是被删除块之后位置移动的逐字节相同内容 —— 我对比了删除行与新增行的多重集合,真正新增的测试内容只有 22 行。这 22 行是一个替代测试(固定 DM 后台回复上的来源标签),以及一处 mock 测试脚手架修正,让 一条非阻塞的观察,因为涉及迁移说明,值得维护者看一眼。 描述里说残留的 没有加时序图和改动文件表 —— 一个五文件的纯删除、且形状上文已经说明,两者都不必要。 测试证据这里携带的证据是本 PR 自己的 CI,通过 API 读取。这是无人值守运行,因此我没有构建或执行本 PR 的任何内容;基于同一规则也没有 tmux 截屏(N/A —— 无人值守 CI,而且这里本身也没有 TUI 界面)。该 commit 上没有任何红色检查。 有两点让绿色结果在这里具有实际含义。安装步骤会执行仓库的 prepare 脚本,而它会运行完整的 workspace 构建,因此 诚实的缺口在平台覆盖:本 PR 的 macOS 与 Windows (CI 结论表见上方英文部分的机器可读区域。) 沙箱验证可以定论这一点: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the review itself came back clean; the cap is the fork- Before reading the diff I wrote down what I would do myself: drop the descriptor and the config type field, delete the buffering state machine (maps, timers, retry, terminal/retirement flushes), keep the immediate labelled path, delete the aggregation-only tests, update the docs page, and then go looking for the consumers that a removal like this usually strands — the management UI, the settings schema, anything that enumerates channel config keys. The PR does all of that, and it goes one step further in a way I would have adopted on seeing the code: it also removes the The result is a genuinely better file. The dispatch method goes from roughly 810 lines of interleaved buffering, parking, retry and detachment logic down to twenty, and every branch that survived is one that was already reachable with the feature off. If I picked this up in six months I would thank whoever deleted it — the removed code was correct as far as I can tell, but it was the kind of correct that costs a reader an afternoon. Where I am less settled is not the code, it is the decision. Three things a maintainer should weigh:
None of those is a blocker and I found no correctness bug, no regression on the default path, no security concern and no scope creep, so I am not requesting changes. I am also not approving: this is a cross-repository ⏸️ Deferring to a maintainer. I could not resolve one to @mention — 中文说明Confidence: 3/5 —— 审查本身结果是干净的;这个分数上限来自 fork 在读 diff 之前我先写下了自己会怎么做:移除字段描述符和配置类型字段,删掉缓存状态机(Map、计时器、重试、终态/会话收尾补发),保留即时带标签的路径,删除仅服务于聚合的测试,更新文档页,然后去找这类移除通常会遗漏的消费方 —— 管理界面、设置 schema,以及任何枚举频道配置键的地方。这个 PR 全部做到了,而且比我预想的更进一步,这一点我在看到代码后也会采纳:它还移除了 结果是一个明显更好的文件。dispatch 方法从大约 810 行交织的缓存、暂存、重试与 detached 逻辑降到二十行,而留下来的每一个分支都是在该功能关闭时本来就会走到的。如果六个月后由我接手,我会感谢删掉它的人 —— 被删的代码据我判断是正确的,但那是那种需要读者花一个下午才能确认的正确。 我不那么确定的不是代码,而是这个决定。有三点需要维护者权衡:
这些都不是阻塞项,我也没有发现正确性 bug、默认路径上的回归、安全问题或范围蔓延,所以我不会提交 request changes。我同样不会批准:这是一个跨仓库(fork)的 ⏸️ 转交维护者。 我没能解析出一个可以 @ 的人 —— 本次运行未设置 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head 25dd9b6e.
Required CI is green — Test (ubuntu-latest, Node 22.x), Lint & Static, Integration Tests (no-AK, No Sandbox) and web-shell E2E Smoke completed successfully; only review-pr (the reviewer's own job) is still running.
For a deletion of this size the only question that matters is whether anything still expects the removed thing, and I checked that rather than sampling it:
aggregateBackgroundAgentResponseshas zero references anywhere at this head — it existed in exactly four places at the base (docs/users/features/channels/dingtalk.md,DingtalkAdapter.ts,DingtalkAdapter.test.ts,index.ts) and all four were updated in the same commit. No orphaned helper, timer or map entry is left behind: the aggregation type, both registries (backgroundResponseAggregations,detachedBackgroundResponseAggregations) and everydrain…/schedule…Flushcall site are gone together, and the remaining delivery paths are the two plain helpers (deliverReplyText,deliverProactiveText) plus adispatchBackgroundResponsethat forwards immediately.- Dropping the settings field does not break an existing config.
plugin.management.fieldsis a descriptor list for the management UI, not a validating schema, and there is no strict unknown-key rejection anywhere in the channel option path — so a storedaggregateBackgroundAgentResponses: trueis inert after this change, which is the behavior the description claims ("existing configurations … behave like the old default"). - The one test added (
index.test.ts) pins the management field list by exact equality (clientId,clientSecret,interactiveCards), so re-adding a field to that list is caught rather than drifting silently, andrequiredConfigFieldsis asserted alongside it. - The
+7/-999adapter shape is a net simplification on a path that had real hazards: the ten-minute retirement flush, the disconnect/drain flushes and the retry-after-failed-delivery branch are all gone, so there is no longer a buffer whose lifetime is coupled to session retirement, which was the same family of defect as the queued-delivery work elsewhere in this channel.
No new Critical found. One non-blocking note: docs/users/features/channels/dingtalk.md loses the whole section without a line telling readers who still have the key set that it is now ignored. That is a one-sentence follow-up rather than a gate, and it pairs with the same choice in the sibling prefix removal.
yiliang114
left a comment
There was a problem hiding this comment.
Reviewed the single PR commit 25dd9b6e (+220/-4631, 5 files). No blocking finding. Approving, with three non-blocking notes.
For a deletion this size the only question is whether anything removed is still reachable, still configured, or still documented. I checked each.
Nothing dangles. git grep at head for aggregateBackgroundAgentResponses, BackgroundResponseAggregation, PendingBackgroundResponseTerminal, BACKGROUND_RESPONSE_AGGREGATION, backgroundResponseAggregations and pendingBackgroundResponseTerminals returns zero hits across packages/, scripts/, docs/ and .github/. The only Background* survivors are channel-base's unrelated BackgroundResponseDeliveryTarget / resolveBackgroundResponseDelivery (ChannelBase.ts:98,612,619,652). index.ts lost a management.fields entry, not a named export — DingtalkChannel and downloadMedia are still exported — so no other package's import breaks.
Background delivery still works, which was my main worry going in: DingtalkAdapter.ts:2694-2713 keeps override async dispatchBackgroundResponse with the same non-agent/empty-text early return as base, then calls super.dispatchBackgroundResponse(sessionId, this.formatBackgroundAgentResponse(text, context?.label), context), and the base hook chain (resolveBackgroundResponseDelivery → deliverBackgroundResponseToTarget → pushProactive/deliverBackgroundReply, ChannelBase.ts:609-676) is intact. So the worst case — background runs can no longer reply at all — does not occur. What changes is combined-vs-segmented delivery, and that was opt-in and defaulted off at base (rawAggregateBackgroundAgentResponses === true), so the population that sees a behaviour change is the set that explicitly enabled it.
No orphaned persisted state. Everything removed was in-memory Maps/Sets plus timers; the adapter's only fs usage (mkdirSync/writeFileSync/tmpdir) belongs to the surviving file-upload path.
The added tests are substantive, not a token replacement. The +202 lines add a test subclass that delegates to the real base deliverBackgroundReply instead of stubbing it, a new preserves the resolved source label on a DM background reply case asserting the actual markdown body (the surviving coverage for dispatchBackgroundResponse / formatBackgroundAgentResponse / formatBackgroundAgentLabel), tightened pushProactive call-count assertions, and five file-projector/status-card cases. 207 test blocks survive in the head file, and 20 references to the surviving background-reply surface remain.
Three notes, none blocking
1. A documented, user-settable config key was deleted with no deprecation path. Base both validated aggregateBackgroundAgentResponses and rejected a malformed value with Channel "<name>" aggregateBackgroundAgentResponses must be a boolean.; head neither reads it, warns about it, nor documents its removal (docs/users/features/channels/dingtalk.md lost 20 lines and gained none). A user whose settings still carry "aggregateBackgroundAgentResponses": true silently moves from one combined message to one message per segment, with no stderr line and nothing in the docs to explain it. Startup is not broken — I checked that no strict unknown-key validator exists (channel-registry.ts:291-295 validates only the plugin's declared management fields, and routes/workspace-channel-management.ts has no unknown-field rejection) — so this is silence, not failure. A one-time warning when the key is present, or one line in the docs under "Background Agent output", would close it.
2. The commit subject carries no ! even though the PR body files this under "Breaking changes / migration notes" and a user-settable key stops working. The sibling PR #11571, removing a config key in the same area, uses feat(channels)!:. Worth aligning so release tooling and changelog readers see both removals the same way.
3. index.test.ts:6-10 pins the whole management field list with toEqual(['clientId','clientSecret','interactiveCards']). That couples this deletion guard to every future field addition — #11561 (the shared output-policy work) will add an output-mode field and trip this test, with a failure message pointing at a deletion guard rather than at the real edit. Asserting the negative (not.toContain('aggregateBackgroundAgentResponses') plus toContain for the three retained keys) would keep the guard and drop the coupling.
Two things I could not verify
The deleted deliverBackgroundReply and onSessionRetiring overrides: DingTalk now falls back to ChannelBase's implementation, and I did not read the deleted bodies within budget. The plausible unconfirmed failure mode is an over-limit long background reply, if the deleted override used the adapter's own chunk-splitting machinery (createReplyTextDelivery / deliverReplyText, head :1596-1700) rather than the base's single-shot send. Session-scoped cleanup of sessionMentionTargets and reaction state survives in a neighbouring method (:2690-2692), which suggests the deleted onSessionRetiring was aggregation-only, but I did not confirm it.
Also checked, since both PRs touch DingtalkAdapter.test.ts: #11570 and #11571 do not conflict — git merge-tree --write-tree --name-only pr-11570-review pr-11571-review exits 0 with no conflicted-file section, and the two edit disjoint regions (#11570 the vi.mock('@qwen-code/channel-base') delegation block and a DM background-reply case; #11571 only the DingtalkChannel quoted media describe block). Landing order between them does not matter.
CI at head: green apart from review-pr (bot orchestration). No unresolved review threads. I did not run the suite locally — no node_modules in this checkout.
中文说明
审了唯一的 PR 提交 25dd9b6e(+220/-4631,5 个文件)。没有阻塞项。 批准,附三条非阻塞说明。
这么大的删除量,唯一的问题是「被删的东西是否仍可达、仍被配置、仍被文档描述」。我逐项查了。
没有任何悬空引用。 在 head 上 grep aggregateBackgroundAgentResponses、BackgroundResponseAggregation、PendingBackgroundResponseTerminal、BACKGROUND_RESPONSE_AGGREGATION、backgroundResponseAggregations、pendingBackgroundResponseTerminals,在 packages/、scripts/、docs/、.github/ 下全部零命中;唯一存活的 Background* 是 channel-base 里无关的 BackgroundResponseDeliveryTarget / resolveBackgroundResponseDelivery。index.ts 少的是一个 management.fields 条目而不是具名导出——DingtalkChannel 与 downloadMedia 仍在——所以不会破坏其它包的 import。
后台投递仍然可用,这是我进来时最担心的一点:DingtalkAdapter.ts:2694-2713 保留了 override async dispatchBackgroundResponse,早退条件与 base 相同,随后调用 super.dispatchBackgroundResponse(...),而 base 的钩子链(resolveBackgroundResponseDelivery → deliverBackgroundResponseToTarget → pushProactive/deliverBackgroundReply,ChannelBase.ts:609-676)完整。所以最坏情况(后台运行彻底无法回复)没有发生。变化的是「合并 vs 分段」投递,而它在 base 上是可选且默认关闭的(rawAggregateBackgroundAgentResponses === true),因此行为变化的群体正是显式开启过它的人。
没有孤立的持久化状态。 被删的全是内存里的 Map/Set 加定时器;adapter 唯一的 fs 使用(mkdirSync/writeFileSync/tmpdir)属于存活的上传路径。
新增测试是实质的,不是象征性替换。 +202 行加了一个委派给真实 base deliverBackgroundReply(而不是 stub)的测试子类、一个断言真实 markdown 正文的 preserves the resolved source label on a DM background reply 用例(这是 dispatchBackgroundResponse / formatBackgroundAgentResponse / formatBackgroundAgentLabel 存活的覆盖)、收紧的 pushProactive 调用次数断言,以及五个 file-projector/status-card 用例。head 文件里存活 207 个测试块。
三条说明,都不阻塞
1. 一个有文档、用户可设的配置键被删除且没有任何弃用路径。 base 既校验 aggregateBackgroundAgentResponses、也会以 Channel "<name>" aggregateBackgroundAgentResponses must be a boolean. 拒绝非法值;head 既不读它、不告警、也不记录它的移除(docs/users/features/channels/dingtalk.md 删了 20 行、一行未加)。settings 里仍写着 "aggregateBackgroundAgentResponses": true 的用户会静默地从「一条合并消息」变成「每段一条」,stderr 没有一行、文档也没有任何解释。启动不会坏——我确认过不存在严格的未知键校验(channel-registry.ts:291-295 只校验插件声明的管理字段,routes/workspace-channel-management.ts 没有未知字段拒绝)——所以这是沉默,不是失败。键存在时给一次性告警,或在文档「Background Agent output」下加一行,就能补上。
2. 提交标题没有 !,尽管 PR 正文把它归在「Breaking changes / migration notes」下、且一个用户可设的键失效了。同区域删配置键的姊妹 PR #11571 用的是 feat(channels)!:。对齐一下,发布工具与读 changelog 的人才能同样看待这两次移除。
3. index.test.ts:6-10 用 toEqual(['clientId','clientSecret','interactiveCards']) 钉死了整个管理字段列表。 这把删除守卫和今后每一次字段新增耦合起来——#11561(共享输出策略)会加一个 output-mode 字段并触发这个测试,而失败信息会指向一个删除守卫而不是真正的改动处。改成断言否定式(not.toContain('aggregateBackgroundAgentResponses') 加对三个保留键的 toContain)能既保住守卫又去掉耦合。
两点我未能验证
被删的 deliverBackgroundReply 与 onSessionRetiring 覆写:DingTalk 现在回落到 ChannelBase 的实现,而我在预算内没有读被删的函数体。未确认的可能失败形态是超长的后台回复——如果被删的覆写用的是 adapter 自己的分块机制(createReplyTextDelivery / deliverReplyText,head :1596-1700)而不是 base 的单次发送。会话级清理(sessionMentionTargets 与 reaction 状态)在相邻方法里存活(:2690-2692),这暗示被删的 onSessionRetiring 只服务于聚合,但我没有确认。
另外,因为两个 PR 都改 DingtalkAdapter.test.ts,我查了:#11570 与 #11571 不冲突——git merge-tree --write-tree --name-only pr-11570-review pr-11571-review 退出 0 且没有冲突文件段,两者改的是不相交区域(#11570 改 vi.mock('@qwen-code/channel-base') 委派块与一个 DM 后台回复用例;#11571 只改 DingtalkChannel quoted media 那个 describe 块)。两者之间的合并顺序无所谓。
head 上的 CI:除 review-pr(机器人编排)外全绿。无未解决的 review 线程。我没有在本地跑测试套件——这个 checkout 没有 node_modules。
What this PR does
Removes the obsolete DingTalk background aggregation setting, its buffering, timers, retries, terminal/retirement flushes, dedicated tests and configuration documentation. Retains immediate, individually labeled Agent responses and the existing reply/proactive transports, attachments, message splitting and interactive cards.
This is the standalone cleanup prerequisite for #11561. It adds no output modes or shared output policy.
Why it's needed
The old concatenate-all-responses behavior is no longer needed. Removing it independently keeps deletion review and migration separate from the new output-policy design.
Reviewer Test Plan
How to verify
Evidence (Before & After)
The published 0.23.2 offline baseline held Agent text when aggregation was enabled, then emitted a combined result at the terminal event. The local built adapter sends the same sequence immediately. Eight configuration/route cases pass; empty terminals and retirement/disconnect add no extra messages. These are intercepted transport checks, not real DingTalk sends.
Local validation: 1,425 tests passed across DingTalk, the shared channel base and CLI configuration/management; full build, typecheck, bundle, changed-file lint, formatting and diff checks passed. Two self-audit passes and an independent review found no issues. See the separate verification report for details.
Tested on
Environment (optional)
Node.js 22.17.0 in an isolated worktree. Published baseline and built-adapter checks intercept transport without credentials or network sends.
Risk & Scope
Linked Issues
Cleanup prerequisite for #11561. The output-policy PR will absorb this deletion once it is merged into main.
中文说明
本 PR 的改动
删除旧钉钉后台聚合设置,以及对应的缓存、计时器、重试、终态和会话收尾补发、专属测试与配置文档。保留即时逐段发送且带 Agent 标签的响应,以及现有回复/主动投递通道、附件、消息分片和交互卡片。
这是 #11561 的独立前置清理 PR,不增加输出模式或共享输出策略。
为什么需要
旧的拼接全部回复逻辑已不再需要。单独删除可将清理评审与迁移影响同新的输出策略设计分开。
评审测试计划
如何验证
前后对比证据
已发布 0.23.2 离线基线在启用聚合时缓存 Agent 文本,再于终态事件发出合并结果。本地构建后的适配器对相同序列即时发送。八种配置/路由组合全部通过;空终态和会话退出、频道断开不额外发消息。这些是拦截传输的验证,不是真实钉钉发送。
本地验证:钉钉、共享频道基类及 CLI 配置/管理共 1,425 个测试通过;全仓构建、类型检查、打包、改动文件 lint、格式及差异检查通过。两轮自审及一次独立评审均未发现问题。详情见独立验证报告评论。
测试平台
环境
Node.js 22.17.0、独立 worktree。已发布版本基线及本地构建适配器检查均拦截传输,不使用凭证或发送网络请求。
风险与范围
关联事项
#11561 的前置清理。此 PR 合入 main 后,输出策略 PR 将吸收这部分删除。