feat(daemon): add batch skill toggle API - #8664
Conversation
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
🩺 serve daemon A/BBuilt the PR base vs this PR head
|
| field | PR base (before) | this PR (after) |
|---|---|---|
features[] |
— | "workspace_skill_batch_toggle" |
— Qwen Code · serve A/B
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.7)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.7)
|
@qwen-code /takeover |
|
🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the 中文说明🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 |
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #8664One commit this round: Resolved this round (new code changes)
Re-verified as resolved by the prior commit (
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts, the PR's only integration-level coverage) did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.7)
|
🤖 Addressed the latest review feedback (round 2/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 2/100 轮)。改动内容与我反驳保留之处如下: Review feedback addressed — PR #8664Round 3 review raised 14 Findings and resolutions
Mutant spot-checks (mutate → test must fail → restore): three highest-risk deviations were verified by temporarily applying the mutants: dropping the Reviewer summary note ("Integration Tests (CLI, No Sandbox) was skipped in CI and did not run locally"): informational about the reviewer's own coverage, not an inline finding; this round is test-only with no production-code change, so unit suites fully exercise the touched behavior and the workflow's CI remains the integration gate. VerificationCommands actually run (all after the final code state):
中文说明已处理的审查反馈 — PR #8664第 3 轮审查提出了 14 条 发现与解决方式
变异体抽查(变异 → 测试必须失败 → 还原):对三处风险最高的偏差做了临时变异验证:删除 审查总结备注(“Integration Tests (CLI, No Sandbox) 在 CI 被跳过且未在本地运行”):这是关于审查者自身覆盖范围的信息说明,并非行内发现;本轮为纯测试改动、无生产代码变更,上述单元测试套件已完整演练受影响行为,集成验证由工作流的 CI 作为最终闸门。 验证实际执行的命令(均针对最终代码状态):
Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
…odex/batch-skill-toggle # Conflicts: # packages/cli/src/serve/run-qwen-serve.test.ts # packages/cli/src/serve/workspace-qualified-rest.test.ts
|
@qwen-code /triage |
|
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: 1678 passed · 0 failed · 1678 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:1678 通过 · 0 失败 · 1678 总计 Verification reportPR #8664 Deep Verification — feat(daemon): add batch skill toggle APIVerdict: Assertion totals: targeted gates 1275 (cli, 6 files) + 322 (sdk, 2 files); A/B harness head arm 38, base arm 23; SDK end-to-end harness 9; mutation matrix 11 (10 mutants + 1 positive control, each a scripted kill-check). 中文摘要
Central claim and A/B proofCentral claim: a capability-gated collection route ( Secondary claims: (1) The A/B boots a real daemon on each arm (
Reviewer Test Plan mapping: step 1 → C1; step 2 → C3+C4; step 3 → C5; step 4 → C7; step 5 → C9 end-to-end for the primary runtime plus unit-level secondary-runtime pinning (see Not covered). The "5/9 flip" shape: every batch cell that is 200-on-head is 404-on-base with settings untouched, while the pre-existing surfaces (capabilities single tag, single toggle, qualified-route mismatch convention) behave identically on both arms. SDK claim (secondary) — compiled Clarification (description vs commits — not a code finding)The PR body calls batch processing "intentionally best-effort" while commit FindingsNone blocking. No behavior contradicts the design doc, protocol docs, or SDK docs — all three were diff-reviewed against the wire behavior observed in the harnesses (route paths, 100-cap, dedupe rule, error codes Observations that are not findings:
Mutation matrix (vacuity proof)Every mutant is a single-point weakening of one PR guard, applied to the HEAD source, run against the suite that should catch it, then restored byte-identically (
10/10 mutants killed, 0 survivors; PC1 proves the harness can fail a suite (the unmutated control run of the same three files was green: exit 0). M7's kill surfaces as a rejection ( Targeted gates
Not covered
MethodologyEnvironment: the CI verify container ( Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
Gate re-run — new head ( Template: complete ✓ Problem: unchanged — a feature, not an observed-bug fix, so the bar is "does a real consumer need this?". In-tree nothing exercises batch semantics yet (the webui/desktop Skill panels flip one checkbox at a time against the single-Skill route) and there is no linked issue; the consumer is the external remote Skill-manager surface. Maintainer @wenshao has since validated the motivation end-to-end — including a real-environment verify report at this exact head — so the need call stands on the maintainer's judgment, which is what this gate asks for. Direction: unchanged — the daemon/SDK surface is an active roadmap area, and a capability-gated additive endpoint mirrors how this contract has grown. It does extend the public daemon contract (new capability tag) and the published SDK surface (new exported types and client methods), which is why the core-module gate keeps this on the maintainer-sign-off track. Size: re-verified from per-file stats at this head — 602 production-logic lines (561 added / 41 deleted) across two packages ( Approach: unchanged — everything in the diff serves the feature (routes ×2, one facade method, one locked batch persist, the capability tag, SDK helpers, docs, tests); no drive-bys, no unrelated churn. The honest design question stands as before: an SDK-side helper looping the single-toggle call would cover the orchestration need with zero daemon changes; what this PR buys over that is one settings write and one session refresh instead of N. The maintainer's approval at this head answers that question knowingly. Risk: no high-risk path matches from the revert-history signal. Both batch routes inherit the existing strict-mutation, trusted-runtime, bearer-auth, and client-identity gates. Moving on to code review. 🔍 中文说明门禁复查——新 head( **模板:**完整 ✓ **问题:**不变——是 feature 而非已观测 bug 的修复,标准是"是否有真实使用方需要它"。仓库内目前没有任何东西用到批量语义(webui/desktop 的 Skill 面板都是逐个复选框调用单条接口),也没有关联 issue;使用方是外部的远程 Skill 管理端。维护者 @wenshao 此后已端到端验证了动机——包括在该 head 上的真实环境验证报告——因此需求判断落在维护者的判断之上,这正是本门禁所要求的。 **方向:**不变——daemon/SDK 面是活跃的 roadmap 区域,capability 控制的增量接口符合该契约一直以来的扩展方式。它确实扩展了公共 daemon 契约(新 capability 标识)与已发布 SDK 面(新导出类型与客户端方法),这正是核心模块门禁将其保留在维护者确认轨道上的原因。 **规模:**按该 head 的文件统计重新核实——602 行生产逻辑(新增 561 / 删除 41),横跨两个 package( **方案:**不变——diff 中所有改动都服务于该功能(双路由、一个 facade 方法、一次加锁批量持久化、capability 标识、SDK 辅助方法、文档与测试),无顺手改动、无无关变更。设计层面的诚实疑问与上次相同:SDK 侧循环调用单条接口即可零 daemon 改动地覆盖编排需求;本 PR 换来的是"一次设置写入 + 一次会话刷新"替代 N 次。维护者在该 head 上的批准已是对该问题的知情回答。 **风险:**revert 历史信号无高风险路径命中。两条批量路由沿用现有严格变更、可信运行时、bearer 鉴权与客户端身份门禁。 进入代码审查。🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewRe-run at the new head ( My independent baseline for this feature: a collection route that validates the batch once, then either loops the existing single-toggle service method (cheap, but N settings writes and N session refreshes) or adds a dedicated batch persist for one-write/one-refresh semantics — gated exactly like the single-toggle path, with per-target best-effort outcomes. The PR takes the second path, the justified end of that design space, so I reviewed it on execution. No critical blockers found. Verified against the current diff:
Minor, non-blocking observations stand: per-target Files changed (24)
Testing evidenceCI-path run — I did not build or execute the PR's code. Evidence below is (a) the PR's own CI on the reviewed commit via the API, fully settled and green; (b) the maintainer's live-environment verification of this exact head; and (c) the sandboxed verification lineage. The author's self-reported local numbers remain their claim, corroborated by (b) and (c).
CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 中文说明代码审查在新 head( 我的独立基线:集合路由做一次批量校验,然后要么循环现有单条 toggle 服务方法(便宜但 N 次写入 + N 次刷新),要么新增专用批量持久化获得"一次写入/一次刷新"语义——门禁与单条路径完全一致,逐目标 best-effort。PR 选了后者,即该设计空间中合理的一端,因此审的是执行。 **未发现关键阻断项。**对当前 diff 逐条复核:
次要、非阻断观察依旧成立:持久化层用 JSON.stringify 比较判断 changed(≤100 项下无碍);facade 一处 测试证据CI 路径运行——未构建或执行 PR 代码。证据为:(a) 审查 commit 上 PR 自身 CI(API 获取,已全部结束且为绿);(b) 维护者对该 head 的真实环境验证;(c) 沙箱验证链。作者自报的本地数字仍是其声明,已由 (b)、(c) 印证。
CI 全绿,见上方表格(由 finalize 任务维护)。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean re-review at a head whose production code is byte-identical to a commit already proven merge-ready in the sandbox, with a maintainer real-environment verification and a commit-pinned approval on top; nothing blocking — the one judgement call, the policy one, is stated below. Reflection for this pass:
The policy question, plainly. The prior pass withheld bot approval at 3/5 under the core-module escalation (cross-package, 602 production lines) and said releasing the vote was a policy call for the skill owners or a maintainer sign-off. Since then the maintainer did more than sign off: he ran independent sandboxed and real-environment verifications of this exact commit and pinned an ✅ Verdict: approve — submitting the approval pinned to the reviewed commit (CI fully settled, no pending 中文说明置信度:4/5 —— 干净的复查:该 head 的生产代码与已在沙箱中证明 merge-ready 的 commit 逐字节一致,且维护者已在真实环境验证并提交了指向该 commit 的批准;无阻断项——唯一的判断项是下述政策性决定。 本次反思:
政策问题,直说。上次在核心模块升级(跨包、602 行生产代码)下以 3/5 保留 bot 批准,并说明放行该投票是 skill 维护者的政策决定或需要维护者签字。此后维护者所做的超过了签字:他对该 commit 独立执行了沙箱与真实环境验证,并提交了绑定该 commit 的 ✅ 裁决:批准——提交绑定被审查 commit 的批准(CI 已全部结束,无待决 — Qwen Code · qwen3.8-max Reviewed at |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round: no action needed This round triaged all feedback newer than the last evaluation (2026-08-07T17:05:02Z):
No code changes were made and no commit was created. The PR head remains 中文说明Autofix 审查轮次:无需处理 本轮对上次评估(2026-08-07T17:05:02Z)之后的全部反馈进行了分类处理:
本轮未做任何代码修改,也未创建任何提交。PR 的 head 仍为 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Verification report — built a real daemon environment locallyVerdict: behaves exactly as described. All 5 reviewer test-plan items pass against a real HarnessNot mocks — a real daemon, real Skills on disk, real settings writes.
Reviewer test plan — 1:1 results
Beyond the test planOne locked write per batch — measured, not assumed. I polled
No lost updates. 6 concurrent batch requests on 6 distinct Skills → all 6 land. 2 batch requests racing 2 per-Skill requests → all 5 land. Route shadowing. I installed a Skill literally named Live ACP session. With a session alive, a 3-target batch (2 valid, 1 missing) returns Gates. No bearer token → 401. Boundaries. 100 names → 200; 101 → 400 Regression surface — the
Field-for-field unchanged. The only difference is JSON key order in the two error payloads ( Test suites I re-ran
Test-to-code ratio is good: ~430 added production lines against ~1450 added test lines and 24 new Non-blocking observations
None of these block the merge. Reprogit fetch origin pull/8664/head:pr-8664 && git worktree add ../wt-8664 pr-8664
cd ../wt-8664 && npm ci && npm run build
# real daemon: 2 workspaces, bearer token, isolated HOME, mock OpenAI
node packages/cli/dist/index.js serve --port 18664 --no-web --token T \
--workspace "$WSA" --workspace "$WSB"
curl -s -H 'Authorization: Bearer T' localhost:18664/capabilities | jq '.features|index("workspace_skill_batch_toggle")'
curl -s -H 'Authorization: Bearer T' -H 'Content-Type: application/json' \
-d '{"skillNames":["gamma","nope-missing","delta","modelonly","locked-one"],"enabled":false}' \
localhost:18664/workspace/skills/enable | jq
# base A/B in the same worktree
git diff 18b9251 HEAD -- packages/ integration-tests/ > /tmp/pr.diff
git apply -R /tmp/pr.diff && npm run build # → 404 on both batch routes
git apply /tmp/pr.diff && npm run build # → restored中文版(合并参考)验证报告 —— 在本地搭建了真实 daemon 环境结论:行为与 PR 描述完全一致,Reviewer 测试计划 5 项全部通过,无阻断性问题。 文末 5 条非阻断观察,均为文档/契约层面的细节,不是缺陷。 环境不是 mock,是真实 daemon、真实磁盘 Skill、真实 settings 写入。
Reviewer 测试计划逐条结果
测试计划之外的额外验证「一次批次一次加锁写入」是实测出来的,不是推断。 请求期间每 2 ms 轮询
无丢失更新。 6 个并发批量请求(各自一个不同 Skill)全部落盘;2 个批量请求与 2 个单 Skill 请求并发,5 项全部落盘。 路由遮蔽。 我特意装了一个名字就叫 真实 ACP 会话。 会话存活时,3 目标批次(2 有效 + 1 缺失)返回 各类门禁。 无 bearer token → 401; 边界。 100 个名字 → 200,101 个 → 400 回归面 ——
逐字段一致。唯一差异是两个错误响应的 JSON 键顺序( 我重跑的测试
测试与代码比例健康:新增约 430 行生产代码对约 1450 行测试、24 个新 非阻断观察
以上都不影响合并。 |
|
@qwen-code /triage |
|
@qwen-code /review |
| _Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/31243213393)._ |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts) did not run locally. Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts) did not run locally。 未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.21.7)
|
🤖 Addressed the latest review feedback (round 4/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 4/100 轮)。改动内容与我反驳保留之处如下: Autofix review round — PR #8664Two inline suggestions from the automated reviewer (round 4); both addressed. No required/critical findings, no issue-level comments, no failed or still-red checks in the feedback. Feedback points and dispositions[Suggestion] Batch cap pre-dedup semantics unpinned (rc:3740554886) — RESOLVEDFinding verified: Fix: added a Efficacy check: temporarily disabling the raw-length check made the new assertion fail ( [Suggestion]
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed — no blockers. Suggestions are inline.
中文说明
已审查——无阻断问题。 建议见行内评论。
— qwen3.8-max via Qwen Code /review (v0.21.7)
|
🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下: Review feedback — round summaryAddressed all three inline suggestions from the round-5 automated review. All three were probe-verified test-coverage gaps: each one pins a documented batch-toggle contract or guard that previously shipped green under a silent mutant. The changes are test-only — no production code was touched. Feedback points and dispositions
Mutation probesEach new/strengthened test was verified against the exact mutant named in its finding: applying the mutant makes exactly that one test fail (1 failed | 111 passed), and reverting restores 112/112 with an empty Conflict notesNone — Verification
Commit: 中文说明评审反馈 — 本轮摘要已处理第 5 轮自动评审中的全部 3 条行内建议。三条均为经过探针验证的测试覆盖缺口:每一条都固定了一个此前在静默变异下可以全绿通过的、已文档化的批量开关契约或守卫。本次改动仅涉及测试——未触碰任何生产代码。 反馈点及处理结论
变异探针每个新增/加强的测试都针对其反馈中点名的变异做了验证:应用该变异后恰好只有该测试失败(1 failed | 111 passed),还原后恢复 112/112,且生产文件的 冲突说明无 —— 验证(Verification)
提交: Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。 🧠 Handled by Qwen Code · model/模型 |
|
🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run. 中文说明🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline. Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts) did not run locally.
中文说明
已审查。 建议见行内评论。 未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite (integration-tests/cli/qwen-serve-routes.test.ts) did not run locally。
— qwen3.8-max via Qwen Code /review (v0.21.7)
| const persistedByName = new Map( | ||
| persisted.outcomes.map((outcome) => [ | ||
| outcome.skillName.trim().toLowerCase(), |
There was a problem hiding this comment.
[Suggestion] persistedByName is keyed by the normalized skill name, so duplicate case-variant entries in requestedSkillNames collapse to the LAST persist outcome: with ['Review', 'review'] and the skill currently enabled, the persist fn yields {changed: true} then a no-op {changed: false}, the Map keeps the last one, both result items report changed: false, results.some(r => r.changed) is false, and the method skips snapshot invalidation, session refresh, and settings_changed events even though the settings file WAS written. Latent today — both HTTP entry points dedupe case-insensitively before calling — but this public service method's correctness silently depends on a caller-side invariant its interface does not state. Probe-verified: the real service called with ['Review','review'] performed zero refresh calls and zero events despite a non-empty settingsChanges. — Failure scenario: a future or SDK-direct caller passes duplicate case-variant names → settings are written but no invalidation/refresh/settings_changed event fires → live sessions keep stale skill state and UI clients miss the change.
// Option A — derive the batch-level flag from the authoritative signal:
const changed = persisted.settingsChanges.length > 0;
// Option B — dedupe at the top of the method over a Set of seen normalized names.中文说明
persistedByName 以规范化后的 Skill 名称为键,因此 requestedSkillNames 中大小写不同的重复条目会折叠为最后一次持久化结果:若传入 ['Review', 'review'] 且该 Skill 当前已启用,持久化函数先产生 {changed: true}、再产生一次无效的 {changed: false},Map 保留最后一个,两个结果项都报告 changed: false,results.some(r => r.changed) 为 false,方法便会跳过快照失效、会话刷新和 settings_changed 事件——尽管设置文件确实已写入。目前处于潜伏状态(两个 HTTP 入口在调用前都会做大小写不敏感去重),但该公共服务方法的正确性悄然依赖一个其接口并未声明的调用方不变量。探针验证:用 ['Review','review'] 调用真实服务时,尽管 settingsChanges 非空,刷新调用和事件发布均为零次。——失败场景:未来或绕过路由直接调用 SDK 的调用方传入大小写不同的重复名称 → 设置被写入但不触发失效/刷新/settings_changed 事件 → 活跃会话保留过期的 Skill 状态,UI 客户端错过该变更。
— qwen3.8-max via Qwen Code /review (v0.21.7)
| expect(res.status).toBe(400); | ||
| expect(res.body.code).toBe('invalid_client_id'); | ||
| expect(persistDisabledSkillsBatch).not.toHaveBeenCalled(); |
There was a problem hiding this comment.
[Suggestion] All three new batch tests here assert the injected persistDisabledSkillsBatch is NOT called (401/403/400 gates); unlike the single-toggle route, there is no happy-path test asserting it IS called through the service createServeApp builds. Probe-verified: with a type-correct wiring regression injected (createServeApp unconditionally installing the throwing stub), every existing batch test stayed green while a positive probe failed 500-vs-200. — Failure scenario: a type-correct wiring regression in server.ts (e.g. the stub fallback replacing the injected dep) leaves every test in this PR green, and every authenticated, trusted, valid POST /workspace/skills/enable request answers 500 in production.
Suggested fix — mirror the single-toggle positive wiring test: build the app with a real batch persist fn, POST a valid batch with auth + trusted workspace, expect 200 and expect(persistDisabledSkillsBatch).toHaveBeenCalledWith(WS_BOUND, ['review'], false, undefined).
中文说明
此处三个新的批量测试都断言注入的 persistDisabledSkillsBatch 未被调用(401/403/400 门禁);与单 Skill 路由不同,没有一个 happy-path 测试断言它确实通过 createServeApp 构建的服务被调用。探针验证:注入一个类型正确的接线回归(让 createServeApp 无条件安装抛错桩)后,现有全部批量测试仍为绿色,而正向探针以 500 对 200 失败。——失败场景:server.ts 中出现类型正确的接线回归(例如桩回退取代了注入的依赖)时,本 PR 的所有测试依旧全绿,而生产环境中每个已认证、受信任、合法的 POST /workspace/skills/enable 请求都会返回 500。
建议修复——仿照单 Skill 路由的正向接线测试:用真实的批量持久化函数构建应用,带认证与受信任工作区 POST 一个合法批次,断言 200 且 expect(persistDisabledSkillsBatch).toHaveBeenCalledWith(WS_BOUND, ['review'], false, undefined)。
— qwen3.8-max via Qwen Code /review (v0.21.7)
| expectTypeOf< | ||
| Awaited<ReturnType<DaemonClient['setWorkspaceSkillsEnabled']>> | ||
| >().toEqualTypeOf<DaemonSkillBatchToggleResult>(); |
There was a problem hiding this comment.
[Suggestion] This envelope pin is a self-comparison: Awaited<ReturnType<DaemonClient['setWorkspaceSkillsEnabled']>> is exactly the declared return type Promise<DaemonSkillBatchToggleResult> unwrapped, so the assertion compares the type to itself and passes for any shape — the only new wire shape not pinned against a literal. Probe-verified via tsc mutants: renaming envelope field sessionsRefreshed escapes (exit 0) while renaming the literal-pinned sibling DaemonSkillBatchToggleItem.skillName is caught (TS2344); renaming a DaemonSkillBatchToggleErrorCode union member also escapes because the Error pin references the union by name. This is the incomplete fix for the earlier resolvability-only assertions, not a duplicate of that comment. Note: after this change the DaemonClient type import above becomes unused — drop it in the same edit. — Failure scenario: a future rename/removal of an envelope field leaves all surface and behavioral SDK tests green → the SDK envelope silently drifts from the daemon wire shape → SDK consumers read undefined at runtime for the renamed/removed field.
| expectTypeOf< | |
| Awaited<ReturnType<DaemonClient['setWorkspaceSkillsEnabled']>> | |
| >().toEqualTypeOf<DaemonSkillBatchToggleResult>(); | |
| expectTypeOf<DaemonSkillBatchToggleResult>().toEqualTypeOf<{ | |
| enabled: boolean; | |
| activation: 'applied' | 'deferred' | 'partial'; | |
| sessionsRefreshed: number; | |
| sessionsFailed: number; | |
| results: DaemonSkillBatchToggleItem[]; | |
| errors: DaemonSkillBatchToggleError[]; | |
| }>(); | |
| expectTypeOf<DaemonSkillBatchToggleErrorCode>().toEqualTypeOf< | |
| | 'skill_not_found' | |
| | 'skill_not_toggleable' | |
| | 'skill_inactive_extension' | |
| >(); |
中文说明
这条信封类型钉扎是自比较:Awaited<ReturnType<DaemonClient['setWorkspaceSkillsEnabled']>> 恰好就是声明的返回类型 Promise<DaemonSkillBatchToggleResult> 解包后的结果,因此该断言是类型与自身比较,对任何形状都成立——这是唯一一个未按字面量钉扎的新 wire 形状。tsc 突变体验证:重命名信封字段 sessionsRefreshed 可以逃逸(exit 0),而重命名字面量钉扎的兄弟类型 DaemonSkillBatchToggleItem.skillName 会被捕获(TS2344);重命名 DaemonSkillBatchToggleErrorCode 联合类型的成员同样逃逸,因为 Error 钉扎按名称引用了该联合类型。这是对早前“仅断言可解析性”评论的不完整修复,而非该评论的重复。注意:改动后上方的 DaemonClient type-only 导入将不再被使用,请在同一编辑中删除。——失败场景:未来重命名/移除信封字段时,所有接口面与行为 SDK 测试仍为绿色 → SDK 信封悄然偏离 daemon 的 wire 形状 → SDK 使用方在运行时读到 undefined。
— qwen3.8-max via Qwen Code /review (v0.21.7)
| .mockRejectedValue( | ||
| new BridgeChannelClosedError('mid-request (batch toggle)'), | ||
| ), |
There was a problem hiding this comment.
[Suggestion] Neither refresh-failure test ('reports partial activation when the shared batch refresh fails' and this one) pins that settings_changed events are still published (and the snapshot re-invalidated) when the shared batch refresh throws, even though the implementation deliberately publishes after the try/catch. Probe-verified: an early-return-from-catch refactor keeps 15/15 tests green while silently dropping the events and the post-refresh invalidation; asserting publishWorkspaceEvent was called once despite the rejection flips the probe. — Failure scenario: a refactor that early-returns from the refresh catch or gates the publish loop on refresh failure passes every test in the block, while in production the settings file IS written on those paths — subscribers relying on settings_changed keep stale skill state, and skipping the post-refresh invalidation lets a concurrently-committed pre-change snapshot stay cached for the 5s TTL.
Suggested fix — in both failure-path tests inject publishWorkspaceEvent: vi.fn() and assert it was called once with { key: 'skills.disabled', value: ['review'], scope: 'workspace' } despite the refresh rejection; optionally pin the post-failure snapshot invalidation via a getWorkspaceSkillsStatus read after the failed toggle.
中文说明
两个刷新失败测试('reports partial activation when the shared batch refresh fails' 与本测试)都没有钉扎“当共享批量刷新抛错时 settings_changed 事件仍会发布(且快照会再次失效)”,尽管实现是在 try/catch 之后刻意发布的。探针验证:从 catch 提前 return 的重构能让 15/15 测试全绿,同时悄然丢掉事件发布与刷新后的失效处理;而断言 publishWorkspaceEvent 在拒绝下仍被调用一次即可让该突变体显形。——失败场景:从刷新 catch 提前返回、或以刷新失败为条件门控发布循环的重构可以通过该块所有测试,但生产环境中这些路径下设置文件确实已写入——依赖 settings_changed 的订阅方会保留过期 Skill 状态,跳过刷新后失效还会让并发提交的变更前快照在 5 秒 TTL 内继续命中缓存。
— qwen3.8-max via Qwen Code /review (v0.21.7)
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round summary — PR #8664 (no action)No actionable feedback this round; no code changes were made and no commit was created.
中文说明Autofix 轮次总结 — PR #8664(无操作)本轮没有可处理的反馈;未修改任何代码,也未创建任何提交。
Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. (
中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
Verify report (maintainer local round) —
|
| # | Cell | Head | Base |
|---|---|---|---|
| 1 | GET /capabilities advertises workspace_skill_batch_toggle (single tag intact) |
✅ | ✅ asserted absent |
| 2 | Batch toggle {review, missing-skill} → 200, ordered per-target results + errors[skill_not_found] |
✅ 200 | ✅ 404 (route absent) |
| 3 | Dedup: [review, missing-skill, REVIEW] → single review result, first-seen spelling |
✅ | n/a |
| 4 | Validation (empty / >100 / blank / non-boolean / non-array / non-string / missing flag) → all 400, correct code |
✅ 7/7 | n/a |
| 5 | Atomicity: after all 400s, skill still enabled, settings file untouched | ✅ | n/a |
| 6 | Mutation observable: skills.disabled: ["review"] on disk + GET /workspace/skills shows disabled |
✅ | n/a |
| 7 | Idempotence: repeat toggle → changed: false |
✅ | n/a |
| 8 | Re-enable → changed: true, settings cleared, status ok |
✅ | n/a |
| 9 | Single-Skill route regression — still 200 + changed:true (toggle & restore) |
✅ | ✅ |
| 10 | Probe sanity: status route answers 200 with bundled review |
✅ | ✅ |
head 18/18 cells · base 5/5 cells — the endpoint's existence and behavior flip entirely with the PR.
| Evidence |
|---|
| 01-ab-head.png — head cell table (18/18) |
| 02-ab-base.png — base cell table (5/5, incl. the 404 flip) |
| 03-sdk-wire.png — SDK over real wire (5/5) |
SDK surface (real wire)
Compiled @qwen-code/sdk dist/index.mjs against the live daemon: capabilities() pre-flight, setWorkspaceSkillsEnabled(['review','missing-skill'], false) typed per-target result, workspaceByCwd(ws).setWorkspaceSkillsEnabled(['review'], true) through the workspace-qualified route, repeat-enable idempotence — 5/5.
Targeted gates (head)
| Suite | Result |
|---|---|
| cli: workspace-skills + daemon-status-provider | 12/12 ✅ |
| cli: workspace-service facade + workspace-qualified-rest | 146/146 ✅ |
| cli: server + run-qwen-serve | 1136/1136 ✅ |
| sdk: DaemonClient + daemon-public-surface | 324/324 ✅ |
ESLint (changed files) / npm run typecheck (monorepo) |
clean ✅ / ✅ |
| Vacuity (mutation matrix) | 2/2 guards pinned ✅ |
Vacuity detail: neutralizing the dedup guard → toggles a deduplicated Skill batch… goes red (expected "spy" to be called with arguments: [ObjectContaining{…}, ['Review','missing','locked'], false]); neutralizing the 100-cap guard → validates Skill batch request shape… goes red (expected 200 to be 400). Both restored; suite re-green.
Findings
No blocking findings. Notes (not defects):
- N1 — real environment observed
activation: "deferred"(no live ACP child in a fresh daemon);applied/partialrefresh paths are unit-covered. Matches the protocol doc's activation semantics. - N2 — secondary-runtime isolation is unit-covered only (34 tests green); the qualified route was exercised over real wire against the bound runtime.
Docs (qwen-serve-protocol.md, daemon-skill-batch-toggle.md) match the verified implementation on every checkable point (cap counts raw entries pre-dedup, error/400 codes, first-seen-order dedup, no-change-still-applied activation).
Not covered
Live session-refresh (applied/partial with real model session) · true multi-workspace daemon smoke · full repo test suite · Windows/Linux · per-commit attribution (aggregate base..head diff verified).
Methodology
macOS / node v24.18.1. Scratch worktrees at the merge ref (98b6103f34, parents verified: ^1=base tip, ^2=PR head) and baseRefOid, each fresh npm ci + build. Harnesses (batch-toggle-ab.mjs, sdk-wire.mjs) spawn the real daemon with scratch HOME/workspace, drive routes with raw fetch or the compiled SDK, and print a cell table; raw logs and harnesses are preserved alongside this report. Assertions: 28 harness cells + 1618 unit tests + 2 vacuity mutations = 1648 executed, 0 unexpected failures.
Report artifacts: tmp/pr8664-verify-20260809-064055/ (report.md, verdict.txt, assertions.json, harnesses/, logs/, evidence/).
|
@qwen-code /triage |
|
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: 1719 passed · 0 failed · 1719 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:1719 通过 · 0 失败 · 1719 总计 Verification reportPR #8664 Deep Verification (round 3) — feat(daemon): add batch skill toggle APIVerdict: Assertion totals: targeted gates 1294 (cli, 6 files) + 324 (sdk, 2 files); A/B harness head arm 46, base arm 30; SDK end-to-end harness 15; mutation matrix 10 (9 PR-guard mutants + 1 pre-PR positive control, each a scripted kill-check). 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。
Previous-finding status (follow-up round)Round 2 shipped
Central claim and A/B proofCentral claim: a capability-gated collection route ( Secondary claims: (1) Each arm boots a real daemon (
Reviewer Test Plan mapping: step 1 → C1; step 2 → C3+C4; step 3 → C5; step 4 → C7; step 5 → C9 (primary runtime + registry gate; the strongest secondary-runtime form is Not covered). The flip shape holds: every batch cell that is 200-on-head is bare-404-on-base with settings untouched, while all pre-existing surfaces (auth, capabilities single tag, single toggle both routes, enumeration) behave identically on both arms. SDK claim — compiled Corrections (to the round-2 description, not the code)
FindingsNone blocking. Observations recorded for completeness, not findings:
Mutation matrix (vacuity proof)Every mutant is a single-point weakening of one PR-introduced guard applied to HEAD source, run against the suite that should catch it, then restored byte-identically (sha-verified per mutant; tree
9/9 PR mutants + 1 positive control killed, 0 survivors; PC1 proves the harness can turn a suite red, and the unmutated control run of all six cli files was green (1294 passed), so the kills mean something. Every kill failed the intended behavioral assertion, not an import/compile break. M5 escalation (reported per the finer-mutation rule). The first M5 mutant weakened the dedupe lookup key ( Not covered
MethodologyEnvironment: the CI verify container ( Evidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix review round — no action neededThis round found no actionable feedback, so no code changes were made. The PR head remains What was triaged:
No conflict resolution was required ( 中文说明Autofix 审查轮次 — 无需处理本轮没有可处理的反馈,因此未做任何代码改动。PR head 保持为 分诊内容:
无需解决冲突( Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
PR #8664 Deep Verification — daemon batch Skill toggle API (re-verification at new head)Verdict:
|
| # | Finding (2026-08-08 round @ 8c9222c) |
Status @ ee2ec6de85 |
|---|---|---|
| 1 | activation:"applied" when nothing was applied (all-targets-fail batch → 200, empty results) |
stands — re-measured live (200, results:[], 2 errors, no settings write) |
| 2 | Fully-failed batch still HTTP 200; SDK callers must inspect errors |
stands — code/doc unchanged |
| 3 | 100-entry cap enforced before dedup (101 collapsing names still 400) | stands — re-measured live; now pinned by duplicatesOverCap test |
| 4 | Request interleaving not reconstructible from response | stands — code/doc unchanged |
| 5 | HTTP-only (no ACP dispatch entry; parity with single toggle) | stands — code unchanged |
Central claim + A/B proof
Real daemon (compiled CLI, isolated HOME/workspace, bearer token, unreachable mock OpenAI endpoint), head vs base in separate worktrees, each with its own npm ci + npm run build (workspace symlinks verified to resolve in-tree). Assertions read the HTTP body and on-disk .qwen/settings.json and GET /workspace/skills.
| cell group | head (20/20) | base (5/5, expected failures as passes) |
|---|---|---|
| probe | GET /workspace/skills 200, 9 skills |
same |
| capability | workspace_skill_batch_toggle + single tag advertised |
batch tag absent, single present |
| single-skill regression | enable/restore 200 + changed:true |
identical |
| validation (7 payloads) | all 400, zero settings writes | — |
| happy path | mixed batch → ordered results/errors, dedup, per-target outcome |
— |
| mutation | settings.json + status flip to disabled |
— |
| idempotence / re-enable | changed:false no-op; re-enable restores ok |
— |
| obs1 / obs3 re-measure | all-fail → 200 empty results; 101 dupes → 400 | — |
| A/B flip | — | POST /workspace/skills/enable → 404 (route absent) |
SDK wire (5/5): built dist/index.mjs against the real daemon — capabilities() pre-flight, DaemonClient.setWorkspaceSkillsEnabled(['review','missing-skill'], false) → typed result (1 result + 1 skill_not_found), workspace-qualified client.workspaceByCwd(ws).setWorkspaceSkillsEnabled(['review'], true) re-enables, repeat enable → changed:false.
Findings
No new findings. All five previous observations stand (see status table); all are documentation/contract nuances, none block the merge.
Vacuity check — the post-verification tests are load-bearing
| mutation | pinned test | result |
|---|---|---|
facade: throw "Missing persisted Skill batch outcome" → silent continue |
fails the whole batch when a persisted outcome is missing… |
killed — promise resolved instead of rejecting |
route: remove length > MAX_WORKSPACE_SKILL_BATCH_SIZE |
validates Skill batch request shape (incl. duplicatesOverCap) |
killed — expected 200 to be 400 |
Both interface-preserving mutations (no import/compile breakage); files restored, worktree pristine.
Targeted gates
| gate | head |
|---|---|
| CLI batch suites (workspace-skills, workspace-qualified-rest, facade, daemon-status-provider, capabilities-docs-contract) | 159 passed (5 files) |
| CLI serve suites (server.test, run-qwen-serve.test) | 1136 passed (2 files) |
| SDK (DaemonClient, daemon-public-surface) | 324 passed (2 files) |
Total 1619 passed / 0 failed across the changed surface.
Not covered
- Live ACP session refresh counts (previous round measured
sessionsRefreshed:1; unit gatefacade.testcovers the path, incl. the round-5 snapshot-invalidation test) - Concurrency / lost-update sweep (covered last round)
- Windows / Linux · full monorepo suite · web-shell/webui
Methodology
Two isolated worktrees at merge 98b6103f34 and base 3037744602; each ran its own npm ci + npm run build (symlinks verified in-tree). Harnesses (harnesses/batch-toggle-ab.mjs, harnesses/sdk-wire.mjs) boot the real daemon and drive real loopback HTTP; every cell is a scripted pass/fail with the observed oracle; exit codes reflect failures. Screenshots rasterised from raw harness output via scripts/verify-capture.mjs. Raw logs: logs/ in the artifact dir. Full report: tmp/pr8664-verify-20260809-064319/report.md.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
|
🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下: Autofix round: no action (PR #8664)This round triaged all feedback collected after the last evaluation (2026-08-08T22:57:25Z) and found nothing that requires a code change. No commit was created; the PR head remains Feedback triage
Why the five previous-round observations were not addressedThe verification report re-confirmed five observations from the 2026-08-08 round at
The report explicitly states these are "documentation/contract nuances, none block the merge." Observations already have the responses prior rounds chose: #3 is pinned by the State
中文说明Autofix 轮次:无操作(PR #8664)本轮对上次评估(2026-08-08T22:57:25Z)之后收集到的全部反馈进行了分类,未发现任何需要修改代码的内容。未创建任何提交;PR head 仍为 反馈分类
为何本轮未处理上一轮的 5 条观察验证报告复查并确认了 2026-08-08 在
报告明确指出这些均为“文档/契约层面的细节,均不阻断合并”。这些观察已保留此前各轮选择的应对:#3 已由 状态
Deferred non-Critical feedbackCritical-only mode is active after 5 change-producing rounds. The workflow excluded the non-Critical feedback below from this round's actionable sections; the items remain open for human follow-up. Maintainer feedback is deferred only after its author has used 2 regular feedback batches in this window's Critical-only tail; authors at that budget, if any, are named below. ( 中文说明完成 5 个产生改动的轮次后进入仅处理 Critical 的模式。本轮可执行区域已排除下方非 Critical 反馈;这些条目保持开放,留待人工跟进。维护者反馈仅在其本人于本窗口 Critical-only 阶段已使用 2 批常规反馈预算后才会延后;达到预算的作者(如有)在下方点名。(评论 Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。 🧠 Handled by Qwen Code · model/模型 |
|
Released in v0.21.9. |
















What this PR does
Adds a capability-gated daemon endpoint and matching typed SDK helpers to enable or disable up to 100 loaded Skills in one request. The response preserves successful single-Skill results and reports errors per target, so one invalid target does not prevent later targets from being processed. Both primary and workspace-qualified routes retain the existing trust, authentication, client identity, and runtime ownership gates.
Why it's needed
Remote Skill managers currently have to orchestrate one request per Skill and cannot receive one structured batch outcome. This change lets admin surfaces close or reopen multiple Skills through the daemon contract while keeping older daemons compatible through a separate capability tag.
Reviewer Test Plan
How to verify
workspace_skill_batch_togglealongside the existing single-Skill capability.enabled: false; expect HTTP 200, two ordered success results, and both Skills to be disabled.errors.Evidence (Before & After)
N/A — non-UI daemon and SDK change. Focused route tests passed (41/41), SDK tests passed (307/307), the capability test passed, and build, typecheck, lint, and diff checks passed locally.
Tested on
Environment (optional)
Node.js 22+ with the repository's clean-install dependency set; daemon route tests used the package-specific Vitest configuration.
Risk & Scope
Linked Issues
N/A
中文说明
这个 PR 做了什么
新增一个受 capability 控制的 daemon 批量接口及对应的类型化 SDK 方法,可在一次请求中启用或停用最多 100 个已加载的 Skill。响应会保留单个 Skill 操作的成功结果,并按目标返回错误,因此某个无效目标不会阻止后续目标继续处理。主工作区和工作区限定路由都沿用现有的信任、认证、客户端身份与运行时归属校验。
为什么需要
远程 Skill 管理端目前必须为每个 Skill 分别发起请求,也无法获得一次批量操作的结构化汇总结果。此改动让管理界面可以通过 daemon 协议批量关闭或重新启用 Skill,同时通过独立 capability 标识保持对旧 daemon 的兼容。
Reviewer 测试计划
如何验证
workspace_skill_batch_toggle。enabled: false的批量请求;预期返回 HTTP 200、两个有序成功结果,并且两个 Skill 都被停用。errors中。证据(修改前与修改后)
不适用——这是非 UI 的 daemon 与 SDK 改动。本地专项路由测试通过(41/41),SDK 测试通过(307/307),capability 测试通过,构建、类型检查、lint 与差异检查均通过。
测试平台
环境(可选)
Node.js 22+,使用仓库干净安装后的依赖;daemon 路由测试使用各 package 对应的 Vitest 配置。
风险与范围
关联 Issue
无