chore(core): cover every hook event in the settings schema and hook bus - #11620
Conversation
The settings schema listed 17 of the 22 hook events the runtime accepts, so PostCompact, PermissionDenied, TodoCreated, TodoCompleted and InstructionsLoaded looked like unknown keys in editors. The hook execution bus handled 14 events and logged "Unknown hook event" for the rest. Add the five schema entries and regenerate the JSON schema, with a test that the schema's hook events equal the HookEventName enum. Add bus cases for SessionStart, SessionEnd, SessionDelete, PreCompact, PostCompact, InstructionsLoaded, StopFailure, TodoCreated and TodoCompleted; the last three return aggregated results, so the bus replies with their final output like every other event. Part of QwenLM#11610
|
Thanks for the PR! Template looks good ✓ — all required headings present, test plan filled in, Chinese translation complete. Problem: observed, and I confirmed it in the tree rather than taking the description's word for it. Direction: aligned. This is precisely the "Reconcile the settings schema and MessageBus dispatch with the event enum" item from #11610, and it stays scoped to that item — the matcher, timeout and stdout items are left to their own PRs. CHANGELOG: no direct reference, but the hooks area is clearly active. Size: core paths touched ( Approach: the scope feels right, and I checked the parts that would fail silently. All nine new For what it's worth, my own first instinct was to derive the schema keys from the enum so drift becomes impossible by construction — but per-event descriptions differ, so that needs a description map plus a completeness check anyway, and the test you added is the cheaper equivalent. A table-driven bus dispatch would be a much bigger rewrite of a ~150-line switch and would cut against the minimal-change rule, so enumerating is the right call here. Your approach matches what I'd have done. Two things I'd push back on. Neither is a correctness bug, but both need a maintainer's call rather than mine, so I've written them up in the review comment: adding the schema entries also flips those five keys from REPLACE to CONCAT during the settings merge, which is a real runtime change that the Risk section currently denies; and the nine new bus cases have no producer today, so they land as unreachable code. Risk: no elevated risk signals — none of the changed files match the revert-correlated path list. Flagging these for discussion before this merges. ⏸️ 中文说明感谢贡献! 模板完整 ✓ —— 所有必需标题都在,测试计划已填写,中文翻译完整。 问题: 是已观测到的问题,我没有只采信 PR 描述,而是在代码树里核对过。 方向: 对齐。这正是 #11610 中"让设置 schema 与 MessageBus 分发和事件枚举保持一致"这一项,而且范围就停在这一项 —— matcher、timeout、stdout 等留给各自的 PR。CHANGELOG 没有直接对应条目,但 hooks 这块明显是活跃区域。 规模: 触及核心路径( 方案: 范围合理,我重点核对了那些出错也不会报错的地方。九个新增 顺带说一下我自己的第一反应:本来倾向于直接从枚举派生 schema 的键,让漂移在结构上不可能发生 —— 但每个事件的描述不同,那样还是得配一张描述表加一个完整性检查,你新增的测试是更省的等价做法。至于总线,改成表驱动分发要重写那个约 150 行的 switch,与最小改动原则相悖,所以这里逐个列举是对的。你的方案和我会做的一致。 有两点我想提出异议。都不是正确性 bug,但需要维护者来判断而不是我来定,所以写在评审评论里了:新增 schema 条目同时会让这五个键在设置合并时从 REPLACE 变成 CONCAT,这是真实的运行时变化,而 Risk 部分目前否认了它;另外九个新增总线分支目前没有任何生产者,落地即是不可达代码。 风险: 无升级风险信号 —— 改动文件都不在"与回滚相关"的路径清单里。 先提出来讨论,再决定是否合入。 ⏸️ — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewI read the diff against the real signatures on 1. The schema entries change settings-merge behaviour, and the Risk section says they don't. This is the one I'd want a maintainer to look at.
So for path
That applies to all five new keys. It is almost certainly the intended semantics — the other 17 events all declare "Main risk or tradeoff: none at runtime for existing callers" and "Breaking changes / migration notes: none" both read as accurate for the bus half of the PR and inaccurate for the schema half. Please name this in Risk & Scope, and ideally pin it with a merge test — nothing in the current suite touches 2. The new test is not prettier-formatted, so the lint gate should fail. In the >({ type: MessageBusType.HOOK_EXECUTION_REQUEST, eventName, input }, MessageBusType.HOOK_EXECUTION_RESPONSE);Prettier can and will break it — the sibling call in the existing Stop-bridge suite a few lines below is already split across lines. For clarity, the four long 3. Nine unreachable branches — a question for the maintainer, not an objection. I traced every producer of Worth knowing: 4. Minor: the bus now has two conventions for aggregated results. Everything else checked out and I want to say so plainly, because these are the failure modes a diff like this invites: the ten new type imports all exist in I also checked the "no user-visible behaviour" claim instead of accepting it, since schema entries can leak into the TUI. They don't: CI test evidenceUnattended CI run — per the triage rules I did not build or execute any PR code. This is the PR's own CI on the reviewed commit, fetched through the API. No check has failed; the two that matter most for this diff ( Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 Two notes on what this signal can and cannot tell us. The Not verified: the merge-behaviour change in finding 1. Nothing in the PR's tests exercises Sandboxed verification would settle exactly that: Real-scenario testing: N/A on this path. This is an unattended CI run, so no tmux capture was driven; the live-behaviour lane is the 中文说明代码评审 我是拿 diff 对着 1. schema 条目改变了设置合并行为,而 Risk 部分说没有。 这是我最希望维护者过目的一点。
所以对路径 这几乎肯定就是预期语义 —— 另外 17 个事件都声明了 "主要风险或权衡:对现有调用方没有运行时影响"和"破坏性变更 / 迁移说明:无"这两句,对总线那一半是准确的,对 schema 那一半不准确。请在 Risk & Scope 中写明,最好再补一个合并测试把它钉住 —— 现有测试完全没有触及 2. 新增测试没有经过 prettier 格式化,lint 门槛应该会失败。 >({ type: MessageBusType.HOOK_EXECUTION_REQUEST, eventName, input }, MessageBusType.HOOK_EXECUTION_RESPONSE);prettier 可以也会把它拆开 —— 下方几行、既有 Stop 桥接测试里的同类调用本来就是跨行写的。 说明一下:你在 3. 九个不可达分支 —— 这是给维护者的问题,不是反对意见。 我追踪了 需要知道的是: 4. 小问题:总线现在对聚合结果有两套写法。 其余部分都核对通过,我想明确说出来,因为这些正是这类 diff 最容易出错的地方:十个新增类型导入都确实存在于 我也没有直接接受"没有用户可见行为"这个说法,而是去验证了,因为 schema 条目有可能渗到 TUI 里。结论是没有: CI 测试证据 无人值守 CI 运行 —— 按 triage 规则,我没有构建或执行任何 PR 代码。以上是被评审提交上 PR 自身的 CI 结果,通过 API 获取。没有任何检查失败;但对本次 diff 最关键的两项( 关于这个信号能与不能说明什么,有两点。 未验证:第 1 点里的合并行为变化。PR 的测试没有任何一处触及 沙箱验证正好可以敲定这一点: 真实场景测试:本路径下 N/A。这是无人值守 CI 运行,没有驱动 tmux 抓取;实时行为的通道是上面点名的 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the code itself is accurate and I could not break it, but it carries an undisclosed runtime change to settings merging on a core config path, and that is a maintainer's call, not mine. Stepping back: this is a competent, well-scoped PR from someone who clearly read the code before writing. Every one of the nine bus cases matches the real So why not 4 or 5. Two reasons, and only the first is substantive. The schema half of this PR is not descriptive-only. Adding those five entries makes Second, and lighter: nine new dispatch branches that no producer can reach. Would I maintain this in six months without cursing the author? Yes. The code is clear, the test is a real completeness guard rather than a coverage filler, and the comment explaining the three aggregated events shows the author was thinking about the reader. The Deferring, not rejecting. Nothing here is wrong enough to request changes over, and the direction is right — I just can't certify a core-config behaviour change that the PR's own risk assessment denies exists. ⏸️ Deferring to @zjunothing, who is already the assigned owner on this PR. Two specific questions I can't settle from the diff, tests and description:
No approval posted in this run. Separately, 中文说明置信度:3/5 —— 代码本身是准确的,我没能找出破绽;但它在核心 config 路径上带来了一处未披露的运行时变化(设置合并语义),这该由维护者判断,而不是我。 退一步看整体:这是一个胜任、范围克制的 PR,作者显然在动手前读过代码。九个总线分支每一个都与真实的 那为什么不是 4 或 5。两个原因,只有第一个是实质性的。 这个 PR 的 schema 那一半并非纯描述性。新增那五个条目会让 第二点,分量较轻:九个没有生产者能触达的新分发分支。 六个月后我来维护这份代码,会骂作者吗?不会。代码清晰,那个测试是真正的完整性守卫而不是凑覆盖率,解释三个聚合事件的注释也说明作者在替读者考虑。新测试里的 是推迟,不是否决。这里没有严重到需要 request changes 的问题,方向也是对的 —— 只是我无法为一处 PR 自身风险评估都否认其存在的核心 config 行为变化背书。 ⏸️ 转交 @zjunothing,他已经是本 PR 的指定负责人。有两个我无法从 diff、测试和描述中敲定的具体问题:
本次运行未提交 approve。另外,在被评审的提交上 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
The five hook events this PR adds to the settings schema also gain the CONCAT merge strategy, so a definition in workspace settings no longer replaces the user's for PostCompact, PermissionDenied, TodoCreated, TodoCompleted and InstructionsLoaded. Pin that with a loadSettings merge test and a schema check that every hook event concatenates, and reword the bus comment about the three aggregated events: they reply with the final output as is, unlike Stop and MessageDisplay.
|
评审意见处理,已推 0363b80(追加提交):
另外回应 stage 3 的两个问题(我是本仓维护者):
|
doudouOUC
left a comment
There was a problem hiding this comment.
Agent-assisted review at dcf82223243c6291c78bd4d606123884aa26248e — no confirmed new Critical findings in the full six-file static scope.
Reviewed against base 35cc3f5f86a97250c81196722ce8780ff396cd9d: schema definitions, generated JSON, merge behavior, nine bus dispatch cases, their HookSystem/HookEventHandler signatures and result shapes, and added test assertions.
The five missing schema entries use the shared hook definition and CONCAT strategy. The settings merge still drops untrusted workspace settings (settings.ts:595-622); adding schema entries does not bypass that boundary. The bus forwards event-specific inputs and cancellation to the Config-owned HookSystem (config.ts:3585-3672), rather than choosing a workspace/runtime from payload data. Base session/cwd input continues to come from that HookSystem. StopFailure/Todo aggregate results are unwrapped to finalOutput; this agrees with the existing todo validation reader (todoWrite.ts:437-477), including its block decision. Existing lifecycle, compaction, instructions and todo producers still use direct methods; these new switch cases add bus parity, not a conversion of every producer to the bus. Missing-system, pre-abort and exception response paths remain available. No daemon route or new ownership selector is added.
History: no previous reviews or threads existed when collected. No new Suggestions. Core gate: 99 changed non-schema production lines; this is not a large external refactor. Write access is verified for the author, but maintainer status is not, and no exemption is assumed.
Validation: static inspection only, plus an independent Python JSON check that all 22 generated event definitions have identical hook-item schemas. The new tests cover event-key completeness, CONCAT parity and dispatch/result forwarding, but were not executed. No live hook process, integration suite or security certification is claimed; payload casts are not runtime input validation.
Comment only — no approval implied.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 1)": tsc --noEmit for packages/core (only packages/cli was typechecked; the ten new core imports were verified used by grep instead).; "agent reverse-audit (round 2)": did not open the module that defines WORKSPACE_RESTRICTED_SETTINGS or the body of stripWorkspaceLoosenings ( settings.ts:575 ); I confirmed via grep that ne…; "agent reverse-audit (round 2)": did not execute npx vitest run on packages/cli/src/config/settings.test.ts , settingsSchema.test.ts , or packages/core/src/config/config.test.ts at the re….
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
Test Plan (not a blocker): Tests 255 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed; Tests 21 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed; 253 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed.
中文说明
仅完成部分审查,审查缺口已披露。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 1)":tsc --noEmit for packages/core (only packages/cli was typechecked; the ten new core imports were verified used by grep instead).;"agent reverse-audit (round 2)":did not open the module that defines WORKSPACE_RESTRICTED_SETTINGS or the body of stripWorkspaceLoosenings ( settings.ts:575 ); I confirmed via grep that ne…;"agent reverse-audit (round 2)":did not execute npx vitest run on packages/cli/src/config/settings.test.ts , settingsSchema.test.ts , or packages/core/src/config/config.test.ts at the re…。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
Test Plan(非阻断):Tests 255 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed; Tests 21 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed; 253 passed — this review observed 30770, 25313, 535, 2025, 1016, 1997, 7341 passed。
— qwen3.8-max via Qwen Code /review (v0.23.3)
|
Reviewed at VerdictApprove — no blocking findings. Purely additive, and it matches the issue item: the settings schema and the message-bus dispatch now cover every hook event. Verified at this commit:
Optional gaps, neither blocking
|
… bus The schema already fails when a hook event lacks an entry; the bus switch had no such guard, so a new event could fall through to the unknown-event default with the suite green. Iterate HookEventName and require each event to reach a hook system method. The direct-call bridge tests now send a real abort signal and assert the same signal is forwarded, cover the no-hook-configured path that replies with no output, use a declared AgentType in the SessionStart example, and give the Todo events two distinct outputs so a first-output projection is caught. StopFailure gets its own case with the aggregate shape the aggregator really returns, and the comment on its arm now says it is fire-and-forget and always replies with no output.
|
@yiliang114 感谢 approve,两条可选缺口的处理如下(58f72d2dea):
同一次提交还处理了机器人这一轮 7 条意见(已逐条回复并 resolve),其中 Risk & Scope 与迁移说明按注册实际读取的作用域重写了。验证: |
yiliang114
left a comment
There was a problem hiding this comment.
APPROVE — no Critical. CI is green at 58f72d2d, all 7 review threads are resolved, and the round-1 ledger carries only a Suggestion. This is a pure-addition diff (+1074/-0) whose one real behaviour change is disclosed precisely.
What I checked
The five new schema entries are structurally identical to the seventeen existing ones. settingsSchema.ts:3903+ adds PostCompact, PermissionDenied, TodoCreated, TodoCompleted and InstructionsLoaded with the same shape as their siblings — type: 'array', category: 'Advanced', requiresRestart: false, default: [], showInDialog: false, items: HOOK_DEFINITION_ITEMS, and mergeStrategy: MergeStrategy.CONCAT. Uniformity is the point: because the settings merge looks up each key's strategy in the schema, a missing entry silently fell back to replace, so these five merged differently from every other event. Adding them removes that asymmetry rather than introducing a new one.
The behaviour change is real, narrow, and correctly scoped in the description. The risk section does not just say "now concatenates" — it establishes why the common case is untouched (hook registration already reads user and workspace hooks per scope, each scope registers on its own, identical hooks collapse when the plan is built, and the merged value is only a fallback for a scope with no hooks object at all), then names the exact configuration that is affected: a system-defaults definition of one of these events alongside a system-settings one, where the winning scope used to replace it. It also flags that PermissionDenied, TodoCreated and TodoCompleted hooks can block, which is the part that matters — a newly-registering hook on one of those three can now block an operation that previously was not blocked. That is the right thing to surface, and the migration note tells the affected reader which definition to remove.
The nine new bus cases cannot change current behaviour. They are added arms in a dispatch switch keyed on event name, so they only affect callers that pass those names, and the description states no current caller does — the direct callers of those events bypass the bus. The value is that the two paths become interchangeable instead of one silently answering "Unknown hook event". The three aggregated-result events are handled honestly too: TodoCreated/TodoCompleted reply with the merged final output because that is what their direct callers read, and StopFailure replies empty because its aggregator discards hook outputs — stating that the reply is always empty there, rather than implying it carries something.
The invariant tests are the strongest part of this change. Asserting that the schema declares exactly the HookEventName enum members and that every one of them concatenates, plus a bus test that iterates the same enum and requires each to reach a hook-system method, closes the failure mode that produced this gap in the first place: a new event added to the enum but not to the schema or the bus. Without those, the same silent drift would recur on the next event.
Non-blocking
The description notes the hooks guide's event catalog is updated separately. Worth confirming that follow-up lands, since the schema descriptions are now the only in-repo statement of what these five events do, and showInDialog: false means the settings UI will not surface them.
The round-1 Suggestion about the migration note naming a configuration the change cannot reach reads as addressed by the current wording, which distinguishes the per-scope registration path from the merged-value fallback explicitly. Flagging only so it is on record that I checked it rather than assumed it.
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
APPROVE
已核对 head 58f72d2dea96c8ed09ce2a3cbf70947e9147cef8(vs origin/main = merge-base 28df8b8a78)。
历史项:本 PR 的 7 条线程全是 Suggestion、无 Critical,且都在当前 head 关闭(settingsSchema.ts:3905、config.ts:3652、config.test.ts:13784/13881 四条为作者在本 head 补/改,另三条 outdated 但已按回复落地)。逐条对过当前 head,没有仍站得住的历史项。
独立复查未发现 Critical,具体核过:
- 覆盖度是闭合的:
HookEventName共 22 个成员,config.ts的响应器 case 现有 22 个(:3438–:3674),default:只剩真正的未知事件;settingsSchema一侧由settingsSchema.test.ts:49-53用集合相等(Object.keys(...hooks.properties)vsObject.values(HookEventName))钉住,另有逐事件mergeStrategy的 CONCAT 钉法,二者都不是能空转的断言。 - 新增 9 个分支的实参顺序与
hookSystem.ts签名逐个对得上(fireSessionStartEvent(source, model, permissionMode?, agentType?, signal?)、fireInstructionsLoadedEvent(filePath, memoryType, loadReason, options, signal?)、fireTodoCompletedEvent(todoId, todoContent, previousStatus, allTodos, phase, signal?)等),没有同类型参数互换的空间;duration_ms那两处沿用 #11618 已有的形参位置。 - 分支返回值形态与注释一致:
TodoCreated/TodoCompleted取finalOutput(直连调用方 todoWrite 读的就是finalOutput.decision),StopFailure取finalOutput且hookAggregator.ts:49-61确实把它硬编码成undefined,所以「等待执行但不回输出」的说法成立。 - 不会双触发:
SessionStart/InstructionsLoaded/TodoCreated的直连调用方(client.ts:2179/2186、instructionsLoadedCallback.ts:30、todoWrite.ts:426/525)直接走hookSystem,不经总线;总线的发布方是Session.ts、client.ts、message-display-dispatcher.ts、toolHookTriggers.ts,同一事件不会被两条路各发一次。 - 生成的
packages/vscode-ide-companion/schemas/settings.schema.json五个新键(PostCompact/PermissionDenied/TodoCreated/TodoCompleted/InstructionsLoaded)都已出现,CI 的 “Check settings schema is up-to-date” 步骤在Lint & Static作业内且已通过。
CI:required 全部完成且成功 —— Test (ubuntu-latest, Node 22.x)、Lint & Static (ubuntu-latest, Node 22.x)、Integration Tests (no-AK, No Sandbox)、web-shell E2E Smoke (ubuntu-latest, Node 22.x)(另有 TUI parity snapshots、OpenTUI no-flicker gate 亦 success),无失败、无 pending(review-pr 属评审机器人自身档,非门禁项)。
一处环境说明:packages/core/src/config/config.test.ts 在本机无法收集(本地 node_modules 缺 @modelcontextprotocol/client,与本 PR 无关),因此该文件的绿色以 CI 的 Test 档为准;上面结论均来自静态核对与 schema/聚合器代码本身。
What this PR does
The settings schema and the hook execution message bus now cover every hook event. The
hookssetting gains entries forPostCompact,PermissionDenied,TodoCreated,TodoCompletedandInstructionsLoaded, so editors that validate against the generated JSON schema stop flagging them as unknown keys and show a description. Like the other 17 events, the new entries use theCONCATmerge strategy, so the mergedhookssetting keeps these five events' definitions from every scope instead of letting the highest-precedence scope replace the others. The bus dispatch gains cases forSessionStart,SessionEnd,SessionDelete,PreCompact,PostCompact,InstructionsLoaded,StopFailure,TodoCreatedandTodoCompleted, which previously fell through to "Unknown hook event". For the last three, whose fire methods return an aggregated result, the bus replies with the final hook output as is: forTodoCreatedandTodoCompletedthat is what their direct callers read, and forStopFailurethe aggregator discards hook outputs, so the reply is always empty.A schema test now asserts that the
hookssetting declares exactly the events in theHookEventNameenum and that every one of them concatenates across scopes, and a settings loading test pins the concatenation, so a future event cannot be added to one without the other or silently fall back to replacing. A bus test iterates the same enum and requires every event to reach a hook system method, so a new event cannot fall through to the unknown-event default either.Why it's needed
The runtime already accepted all 22 events in settings, but the schema listed 17, so valid configurations looked wrong in editors and the settings docs generator had nothing to describe for the other five. Because the settings merge looks up each key's merge strategy in the schema, the five missing events also merged differently from the rest: the highest-precedence scope replaced the others. The bus handled 14 events, so anything that dispatches hooks through it, rather than calling the hook system directly, could not fire the other eight at all. No current caller relies on the new bus cases; they close the gap so the two paths stay interchangeable.
Reviewer Test Plan
How to verify
cd packages/cli && npx vitest run src/config/settingsSchema.test.tsincludes the checks that the schema's hook events equal the enum and that every hook event usesCONCAT.cd packages/cli && npx vitest run src/config/settings.test.ts -t "concatenate hook definitions"loads user and workspace settings that both definePostCompactandTodoCreatedhooks and checks that the merged settings keep both scopes' definitions in order.cd packages/core && npx vitest run src/config/config.test.ts -t "hook execution bridge"routes every hook event through the bus against a stubbed hook system, and for the nine new cases checks the forwarded arguments and abort signal, the reply when no hook is configured, the merged final output forTodoCreatedandTodoCompleted, and the empty reply forStopFailure.npm run generate:settings-schemareproduces the committed JSON schema.Evidence (Before & After)
The user-visible change is the settings merge for the five new schema keys; the rest affects editor validation and bus dispatch that no current caller uses. The evidence is the unit tests and the regenerated schema.
Each new bus assertion was checked by breaking the code it pins, then restoring it: adding an enum member without a bus case fails the coverage test, forwarding a different signal field fails 15 cases, and normalizing an empty
SessionEndresult, replying with the first Todo hook's output instead of the merged one, publishing an output forStopFailure, and a non-AgentTypevalue in theSessionStartexample each fail their case. The fullconfig.test.tspasses (719 tests).With the
CONCATstrategy removed from thePostCompactandTodoCreatedschema entries, which is howmainmerges them, both new tests fail:The generated
settings.schema.jsondiffers frommainonly by the five new keys underhooks:InstructionsLoaded,PermissionDenied,PostCompact,TodoCompleted,TodoCreated. Withconfig.tsandsettingsSchema.tsreverted tomain, the nine new dispatch cases and the schema coverage check fail.Tested on
Environment (optional)
N/A, unit tests only.
Risk & Scope
hookssetting now concatenatesPostCompact,PermissionDenied,TodoCreated,TodoCompletedandInstructionsLoadedacross scopes, where before the highest-precedence scope replaced the others; this matches the other 17 events. Hook registration reads user and workspace hooks per scope, so a setup where both scopes define hooks is unchanged: each scope already registered on its own, and identical hooks (same name and command or URL) are collapsed when the hook plan is built. The merged value is only a fallback for a scope that has nohooksobject at all. Through that fallback, definitions of these five events from lower-precedence scopes that the winning scope used to replace now also register and run, for example a system-defaultsTodoCreatedhook alongside a system settings one.PermissionDenied,TodoCreatedandTodoCompletedhooks can block. Existing direct callers of the nine bus events are unchanged.hooksobject, remove the definition you do not want.Linked Issues
Part of #11610
中文说明
这个 PR 做了什么
设置 schema 和 hook 执行消息总线现在覆盖所有 hook 事件。
hooks设置新增PostCompact、PermissionDenied、TodoCreated、TodoCompleted和InstructionsLoaded的条目,按生成的 JSON schema 做校验的编辑器不再把它们标为未知键,并能显示说明。新条目和其余 17 个事件一样使用CONCAT合并策略,所以合并后的hooks设置会保留各作用域对这五个事件的定义,不再由优先级最高的作用域替换其它作用域。总线分发新增SessionStart、SessionEnd、SessionDelete、PreCompact、PostCompact、InstructionsLoaded、StopFailure、TodoCreated和TodoCompleted的分支,这些事件以前会落到 "Unknown hook event"。后三个事件的 fire 方法返回聚合结果,总线直接回复其最终输出:对TodoCreated和TodoCompleted来说这正是直接调用方读取的内容;StopFailure的聚合器会丢弃 hook 输出,所以回复总是空的。新增的 schema 测试断言
hooks设置声明的事件与HookEventName枚举完全一致,并且每个事件都跨作用域拼接;另有一个设置加载测试钉住拼接行为。以后新增事件时,两处不会只改其一,也不会悄悄退回替换。另有一个总线测试遍历同一个枚举,要求每个事件都能到达 hook 系统的某个方法,所以新事件也不会落到 unknown-event 默认分支。为什么需要
运行时本来就接受设置里的全部 22 个事件,但 schema 只列了 17 个,合法配置在编辑器里看起来是错的,设置文档生成器也无从描述另外五个。设置合并按 schema 查每个键的合并策略,所以缺失的五个事件合并方式也和其余事件不同:优先级最高的作用域会替换其它作用域。总线只处理 14 个事件,任何通过总线而不是直接调用 hook 系统来分发 hook 的地方,都无法触发另外八个。目前没有调用方依赖新增的总线分支;这里补齐缺口,让两条路径保持可互换。
评审测试计划
如何验证
cd packages/cli && npx vitest run src/config/settingsSchema.test.ts:包含两项检查,schema 中的 hook 事件与枚举一致,且每个 hook 事件都使用CONCAT。cd packages/cli && npx vitest run src/config/settings.test.ts -t "concatenate hook definitions":加载用户级和工作区设置,两者都定义了PostCompact和TodoCreatedhook,检查合并结果按顺序保留了两个作用域的定义。cd packages/core && npx vitest run src/config/config.test.ts -t "hook execution bridge":对桩掉的 hook 系统,通过总线分发每一个 hook 事件;对新增的九个分支,检查转发的参数和 abort signal、未配置 hook 时的回复、TodoCreated与TodoCompleted合并后的最终输出,以及StopFailure的空回复。npm run generate:settings-schema能复现提交的 JSON schema。证据(前后对比)
用户可见的变化是五个新 schema 键的设置合并方式;其余部分只影响编辑器校验,以及目前没有调用方使用的总线分发。证据是单元测试和重新生成的 schema。
settingsSchema.test.ts与settings.test.ts共 255 个用例全部通过;config.test.ts中 "hook execution bridge" 相关用例 50 个通过(跳过 669 个,共 719 个);整个文件 719 个用例全部通过。SessionEnd结果归一化、Todo 分支改为回复第一个 hook 的输出、StopFailure分支发布输出、SessionStart示例用非AgentType的值,都会让各自的用例失败。PostCompact和TodoCreated条目上的CONCAT去掉(即main的合并方式)后,两个新测试都失败(2 failed | 253 passed)。settings.schema.json与main相比只在hooks下多了五个键:InstructionsLoaded、PermissionDenied、PostCompact、TodoCompleted、TodoCreated。config.ts和settingsSchema.ts换回main版本后,新增的九个分发用例和 schema 覆盖检查都会失败。测试平台
仅在 Linux 上本地验证;macOS 和 Windows 未测试。
环境
不适用,仅单元测试。
风险与范围
hooks设置现在对PostCompact、PermissionDenied、TodoCreated、TodoCompleted和InstructionsLoaded跨作用域拼接,以前由优先级最高的作用域替换其它作用域;这与其余 17 个事件一致。注册 hook 时按作用域分别读取用户级和工作区设置,所以两个作用域都定义了 hook 的配置不受影响:各作用域本来就分别注册,完全相同的 hook(名称与命令或 URL 相同)在构建执行计划时会合并为一个。合并值只在某个作用域完全没有hooks对象时作为回退使用。经由这条回退,以前被胜出作用域替换掉的低优先级作用域对这五个事件的定义,现在也会注册并执行,例如 system-defaults 里的TodoCreatedhook 会与 system 设置里的一起执行。PermissionDenied、TodoCreated、TodoCompleted的 hook 可以阻断操作。九个总线事件现有的直接调用方不受影响。hooks对象时替换掉它,请删掉不想要的那条定义。关联 Issue
Part of #11610