feat(core): add permission_mode, agent_id and prompt_id to every hook input - #11618
Conversation
… input Only tool, subagent and session-start events carried permission_mode, and agent_id only appeared on SubagentStart and SubagentStop. Scripts shared across events could not branch on either. The common hook input now includes permission_mode (the session approval mode, unless the event reports its own), agent_id when the event fires inside a subagent, and prompt_id when the model turn is known. PostToolUse and PostToolUseFailure also report duration_ms, measured from the start of tool execution in both the core scheduler and ACP sessions. The three copies of the approval-mode to permission-mode mapping are replaced by one shared helper. Part of QwenLM#11610
|
Thanks for the PR! Template looks good ✓ Problem: observed, not theoretical. This is item 5 of the hook-contract audit in #11610, and the description carries a real before/after capture of hook stdin: installed 0.22.3 yields Direction: aligned, with upstream evidence. Claude Code's changelog is a direct match on two of the four fields: 2.1.119 added Two honest notes on direction, neither a block. The Size: core paths are touched ( Approach: close to the minimal shape, and I independently arrived at the same one before reading the diff — Risk: Stage 1e flags Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 问题: 是已观测到的问题,不是理论性加固。这是 #11610 hook 契约审计中的第 5 项,PR 描述里带了真实的 before/after hook stdin 捕获:已安装的 0.22.3 在 方向: 对齐,且有上游证据。Claude Code 的 changelog 在四个字段中有两个是直接对应的:2.1.119 为 关于方向有两点如实说明,都不构成阻塞。#11610 上的 规模: 触及了核心路径( 方案: 接近最小实现,而且我在看 diff 之前独立想到的也是同一个方案—— 风险: Stage 1e 命中 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewIndependent proposal first. Before opening the diff I worked only from the title and the "Why it's needed" section, and the shape I landed on was: put the three fields in No critical blockers. The things most likely to be wrong in a change shaped like this all check out:
Two suggestions, neither blocking:
sequenceDiagram
participant P1 as Scheduler (core or ACP session)
participant P2 as Tool invocation
participant P3 as Agent and prompt contexts
participant P4 as HookEventHandler
participant P5 as Config
participant P6 as User hook script
P1->>P3: enter promptIdContext for this turn
P1->>P1: stamp executionStartedAt at the invocation boundary
P1->>P2: execute, after validation and approval
P2-->>P1: result, or failure
P1->>P4: fire PostToolUse with durationMs from the stamp
P4->>P3: read current agent id and prompt id
P4->>P5: getApprovalMode for the session mode
P4->>P4: build base input, then spread event fields last
P4->>P6: payload with permission_mode, agent_id, prompt_id, duration_ms
Files changed (16 of 16)
TestingThis was an unattended CI run, so I did not build, run, or execute anything from this PR — the static-review rule applies and the evidence below is the PR's own CI, read through the API, plus the source reads described above. No check is red at the time of writing. The two checks that actually pin this change — Not verified, and worth saying plainly: the before/after payload capture in the description (installed 0.22.3 versus this branch, Sandboxed verification would settle the part the suite cannot: Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 中文说明代码审查。 先说独立方案:在读 diff 之前,我只根据标题和"为什么需要"推出的做法是——三个字段放进 没有阻塞性问题。 这类改动最容易出错的几处都核对过了:八个自带模式的事件都是先展开 base、后写自己的 两条建议,均不阻塞。 其一,改写后的文档说 测试。 本次是无人值守的 CI 运行,因此我没有构建、运行或执行本 PR 的任何代码——静态审查规则适用。下面的证据来自通过 API 读取的 PR 自身 CI,以及上文的源码核对。撰写时没有任何检查是红的。 真正能钉住这次改动的两项检查—— 需要如实说明的未验证项:描述里的 before/after 载荷捕获(已安装的 0.22.3 对比本分支, 沙箱验证可以补上测试套件补不了的那部分: — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 3/5 — the implementation is clean and I would not change it, but this edits a public contract and lands as one of six sibling PRs, so the field-set decision needs a human yes rather than a bot approval. Stepping back: my independent proposal and this PR converged on the same design before I read a line of the diff, which is usually a good sign — it means the change follows the shape of the code instead of fighting it. The problem is real, and I want to be precise about why I believe that rather than just accepting the PR's framing. The guide already promised So why not approve? Three reasons, and only the first two are about the code. The change writes to a payload that every user hook script parses. It is additive and optional, and the docs already declare hook input forward-extensible with a duty to ignore unknown fields — so the contract is designed to absorb exactly this. But "designed to absorb it" is not the same as "a bot should decide it", and I cannot name the external consumers, which is the specific test the core-change gate sets. The PR's own Risk section makes the same point more sharply than I would: a strict decoder that rejects unknown properties has to be updated before upgrading, and for a security-sensitive hook a decoder failure can flip fail-open to fail-closed. That deserves a maintainer's explicit sign-off on the field set. Second, this is one of six PRs opened against #11610 the same day. Splitting a tracking issue into focused PRs is good practice and I evaluated this one on its own merits — but the six share one underlying decision about how far to move the hook contract toward Claude Code's, and one of them (#11619) is a fork Third, and merely procedural: Two small things for the author, neither a reason to hold the PR: the doc sentence about ⏸️ Deferring to @yiliang114 — you are already the assigned owner on this one. The code needs nothing from you; what needs a human call is whether these four fields become part of the documented hook input contract, and whether the six PRs off #11610 should be reviewed as a set. 中文说明置信度:3/5 —— 实现是干净的,我不会去改它;但这次改动动的是一份公共契约,而且是六个同源 PR 中的一个,因此字段集合这个决定需要人来点头,而不是由机器人批准。 退一步看:在读 diff 之前,我独立想到的方案和这个 PR 收敛到了同一个设计,这通常是个好信号——说明改动是顺着代码本身的形状走的,而不是硬掰。 问题是真实存在的,我想说清楚我为什么相信这一点,而不是照单接受 PR 的说法。指南早就承诺子代理里的 hook 事件会带 那为什么不批准?三个理由,其中只有前两个和代码有关。 这次改动写入的是每个用户 hook 脚本都要解析的载荷。它是新增且可选的,文档也已经声明 hook 输入是前向可扩展的、消费方有义务忽略未知字段——所以这份契约本来就是为了吸收这类改动而设计的。但"设计上能吸收"不等于"该由机器人来决定",而我无法点名外部消费方,这正是核心改动闸门设定的判据。PR 自己的风险部分把这一点讲得比我还尖锐:拒绝未知属性的严格解码器必须在升级前放行这些字段,而对安全敏感的 hook 来说,解码失败可能把 fail-open 翻转成 fail-closed。这值得 maintainer 对字段集合明确签字。 第二,这是同一天针对 #11610 开出的六个 PR 之一。把一个跟踪 issue 拆成聚焦的 PR 是好实践,我也是就这一个 PR 本身来评估的——但这六个共享同一个底层决定,即 hook 契约要向 Claude Code 靠拢到什么程度,而其中 #11619 是一个 fork 的 第三,纯属程序性:我审查时 给作者两个小点,都不构成卡住 PR 的理由:关于 ⏸️ 转交 @yiliang114 —— 你已经是这个 PR 的指派负责人。代码层面不需要你做什么;需要人来定的是这四个字段是否成为 hook 输入契约的正式一部分,以及 #11610 拆出的六个 PR 是否应当作为一组来审。 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
duration_ms on PostToolUse and PostToolUseFailure was the difference of two Date.now() readings, so a system clock step while a long tool ran could report a negative or inflated duration to audit hooks. Both the core scheduler and the ACP session now read performance.now() and round the elapsed time. The hooks guide also lists SessionStart among the events that report agent_type.
|
评审意见处理,已推 945dd0f(追加提交):
关于 stage 3 的几点(我是本仓维护者):
|
doudouOUC
left a comment
There was a problem hiding this comment.
Agent-assisted review at 73087750f152d5e9b6c73817218d0c788d953da9 — Partial review — coverage gaps; no confirmed Critical found in the inspected paths.
Reviewed the full 17-file diff and traced the added fields beyond it: session approval mode through the shared converter and event-specific overrides; agent identity through the agent AsyncLocalStorage frame; prompt identity through interactive, non-interactive and ACP producers; duration from both execution boundaries through tool triggers, MessageBus dispatch, HookSystem and HookEventHandler. The MessageBus uses synchronous EventEmitter dispatch, so the common-field read does not inherently lose the publishing async context. The hook runners serialize the resulting input rather than projecting away these fields. Existing explicit subagent identity/mode overrides remain after the common-field spread. The duration clock is monotonic and starts after approval rather than at scheduling.
Suggestion: supplement the helper-level context tests with a real submission/tool-hook path, including an approval-delayed or resumed subagent. The new test manually installs both async-local frames; it does not establish which identity survives a production queue/resume boundary. This is a coverage recommendation, not a confirmed identity defect.
Limitations: I have not exhaustively established async-frame ownership across every detached/background completion, deferred approval and resume path, or measured all failure-path duration boundaries. Thus I cannot make the core gate's 100%-confidence claim. This is a small feature change (163 changed production-source lines by the test/generated-excluding count), not a size-blocked refactor; maintainer review is still needed for the remaining core/cross-package coverage. No changed daemon route was found: the ACP change is within the owning Session's tool execution, not workspace selection or primary-runtime fallback.
There were no earlier reviews/threads in the fetched history to reconcile. Read the added/modified regression tests, but ran no build, unit tests or E2E; no PR code was executed. Comment only; no approval implied.
|
Reviewed at VerdictApprove — no blocking findings. Scope matches the issue item exactly: Verified:
Notes, neither blocking
|
yiliang114
left a comment
There was a problem hiding this comment.
APPROVE — no Critical. CI is fully green at 73087750, there are no review threads, and @doudouOUC's pass found no Critical either. This is a well-scoped change: 163 production lines, and the riskiest part of it is a three-way consolidation, which is exactly where a behaviour change likes to hide — so that is what I checked hardest.
The consolidation is genuinely behaviour-preserving
The three removed copies did not all switch on the same kind of value, which is the thing worth confirming:
client.ts'stoPermissionModeswitched onApprovalModeenum members.tools/agent/agent.ts'sapprovalModeToPermissionModeswitched onApprovalModeenum members.agents/background-agent-resume.ts's copy switched on bare string literals —'yolo','auto-edit','auto','plan','default'.
The new shared helper switches on enum members, so it is only equivalent to the third copy if those literals are the enum's values. They are: config/approval-mode.ts:7-13 defines PLAN = 'plan', DEFAULT = 'default', AUTO_EDIT = 'auto-edit', AUTO = 'auto', YOLO = 'yolo'. So case ApprovalMode.AUTO_EDIT and case 'auto-edit' are the same case, and all three copies collapse to the same function without a behaviour change on any path. Folding ApprovalMode.DEFAULT into default is also equivalent, since both returned PermissionMode.Default.
Worth naming the trap this helper now owns, because it is the reason three copies drifting was a real risk: the two enums spell the same mode differently — ApprovalMode.AUTO_EDIT = 'auto-edit' (hyphen) maps to PermissionMode.AutoEdit = 'auto_edit' (underscore, hooks/types.ts:1033). The docs update gets this right, listing permission_mode as default | plan | auto_edit | auto | yolo. Having one place responsible for that translation is the actual value of the change.
Common-field merge order is correct
createBaseInput emits permission_mode from the session's approval mode plus conditional agent_id/prompt_id, and every event that reports its own value spreads the base first and sets its own after — PostToolUse at hookEventHandler.ts:433-435, SubagentStart at :656-660. So event-specific mode wins, as the body claims, and SubagentStart's explicit agent_id correctly overrides the ambient getCurrentAgentId() frame, which at that point is the parent, not the child being announced. That ordering is load-bearing and it is right.
duration_ms excludes the right interval and degrades correctly
executionStartedAt is stamped from performance.now() at the invocation boundary — inside promptIdContext.run, immediately before setStatusInternal(callId, 'executing') (coreToolScheduler.ts:5271 and :5315, both execution branches) — so validation and approval time are excluded as documented. Monotonic clock means a system clock step during a long tool cannot skew it. elapsedExecutionMs() returns undefined when execution never started, and every consumer uses ...(durationMs === undefined ? {} : { duration_ms: durationMs }), so a tool cancelled before it starts omits the key entirely rather than emitting 0 or null — a hook can distinguish "never ran" from "ran instantly". It is wired at all five scheduler call sites (:5539, :5631, :6086, :6374, :6417), and acp-integration/session/Session.ts mirrors the identical helper at :12105, stamps at :13797 and passes it at :14089, :14158 and :14396 — so the ACP path reports the same field on the same definition rather than a second approximation. The replay path in config.ts:3501 and :3516 type-guards with typeof input['duration_ms'] === 'number' instead of casting, which is the right treatment for a value arriving from serialized input.
Non-blocking
-
@doudouOUC's coverage suggestion is the right one and I'd second it.
hookEventHandler.test.tsinstalls both async-local frames by hand, which pins that the helper reads them but not which identity actually survives a production queue, a deferred approval, or a background-agent resume. Those are precisely the boundaries where anAsyncLocalStorageframe is most likely to be lost, and a lost frame fails silently — the field just goes absent, and sinceagent_idis documented as "only when the event fires inside a subagent", an absent field on a subagent event is indistinguishable from a main-agent event. One integration test through a real submission or tool-hook path with an approval-delayed subagent would close it. Not a confirmed defect, and not a reason to hold this. -
The helper's parameter widened from
ApprovalModetostring | undefined. That is necessary for the persisted background-agent value, but it means a mistyped literal at a future call site silently maps toPermissionMode.Defaultinstead of failing typecheck.ApprovalModeValue(`${ApprovalMode}`,approval-mode.ts:15) is the same string union, so typing the parameterApprovalModeValue | undefinedwould keep the persisted path working and restore the compile-time check at the enum-typed call sites. Minor. -
The hyphen/underscore asymmetry deserves a line in the helper's docblock. It is now the single place that knows
auto-editbecomesauto_edit, and nothing in the file says so. That is the fact a future editor is most likely to break.
The before/after evidence in the description — captured stdin from real qwen -p runs against the installed 0.22.3 release rather than against assertions, with agent_id correctly absent on the main agent — is the right way to demonstrate a hook-contract change.
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings.
Approval blockers: none.
Checked:
permission_modeoverride ordering across all 22 event types. All eight events that carry their own mode (SessionStart,PreToolUse,PostToolUse,PostToolUseFailure,PostToolBatch,PermissionRequest,SubagentStart,SubagentStop) spreadcreateBaseInput()first and then assignpermission_mode— the event-specific value wins in every case. Verified from the live file at head.ApprovalModeenum values ('plan' | 'default' | 'auto-edit' | 'auto' | 'yolo') match the raw string literals used in the three removed local copies (client.ts,agent.ts,background-agent-resume.ts). No regression from the consolidation.elapsedExecutionMs()returnsundefinedwhenexecutionStartedAtis unset (pre-execution failures) —duration_msis correctly omitted in those cases via the spread guard.- Two
executionStartedAt = performance.now()sites incoreToolScheduler.tsare two execution branches of the same invocation, not sequential calls. The singleelapsedExecutionMsclosure captures the sharedletvariable correctly. - Duration test (mock
Date.nowto 0 mid-execution, assertduration_ms ≥ 0): correctly discriminates betweenDate.now()andperformance.now()becauseDate.now()would yield a large negative number at that point.
Not covered: macOS and Windows not tested (author-disclosed); ACP path unit-only (author-disclosed); no terminal-dependent behavior changed so rung 3 not run.
Cross-check vs existing reviews: yiliang114's review at this head reached the same verdict with full coverage of call sites and promptId chain through the bus. No diverging finding found. The minor note about elapsedExecutionMs() capturing post-settle processing time is noted and non-blocking.
Reviewed with AI assistance.
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
APPROVE
已核对 head 73087750f152d5e9b6c73817218d0c788d953da9(vs merge-base 35cc3f5f86)。无 review 线程,历史记录里没有待修的 Critical/必要 Suggestion;独立复查未发现新 Critical。
三处映射合并确认行为不变:client.ts 与 tools/agent/agent.ts 删掉的两份按 ApprovalMode 枚举分支,background-agent-resume.ts 删掉的那份按裸字符串分支;新的 hooks/permission-mode.ts 按枚举分支,而 config/approval-mode.ts:7-13 的值正是 'plan' | 'default' | 'auto-edit' | 'auto' | 'yolo',因此 'auto-edit' 与 ApprovalMode.AUTO_EDIT 是同一分支,三份等价(PermissionMode.AutoEdit 落地为 'auto_edit',与文档里写的取值一致)。git grep toPermissionMode 在该 head 已无残留引用。
公共字段不会盖掉事件自带的值:hookEventHandler.ts 里 22 个事件字面量全部先展开 createBaseInput(...),自带 permission_mode 的 8 处(:326/:401/:435/:476/:534/:597/:658/:689,即 SessionStart/PreToolUse/PostToolUse/PostToolUseFailure/PostToolBatch/PermissionRequest/SubagentStart/SubagentStop)在其后赋值;SubagentStart/SubagentStop 在 :659/:691 覆写自己的 agent_id。agent_id/prompt_id 用条件展开,未知时不产生 undefined 键。duration_ms 一律追加在形参末尾(无位置错位风险),config.ts 的再触发处用 typeof === 'number' 收敛。
本地验证:packages/core 的 hookEventHandler.test.ts + hookSystem.test.ts + permission-mode.test.ts → Test Files 3 passed (3) / Tests 251 passed (251)。变异复核(每个单独注入,/tmp 隔离副本):删掉 createBaseInput 里的 permission_mode → reports the session approval mode on events without their own mode 红;删掉 PreToolUse 自带的 permission_mode → keeps the permission mode an event reports itself 与 should include all parameters in the hook input 红;把 agent_id/prompt_id 改成无条件写入 → adds agent_id and prompt_id only when they are known 红。新契约四个方向都有能变红的见证。
CI:required 全部完成且成功 —— Test (ubuntu-latest, Node 22.x)、Lint & Static、Integration Tests (no-AK, No Sandbox)、web-shell E2E Smoke,另有 TUI parity snapshots 与 OpenTUI no-flicker gate 亦 success。
记录一条非阻塞建议(doudouOUC 提出,不要求本 PR 改):现有上下文用例是手工装上两个 AsyncLocalStorage 帧,建议后续补一条走真实提交/工具钩子路径(含审批延迟或被 resume 的子代理)的用例,以证明身份字段在真实链路上也能存活。
|
@yiliang114 @doudouOUC 感谢两轮核对,三条都不改代码,理由如下:
|
|
Released in v0.23.4. |
What this PR does
Every hook input now carries three more common fields.
permission_modeis the session's approval mode, and events that already reported the mode that applied to them (tool events, subagent events,SessionStart) keep their own value.agent_idis added when the event fires inside a subagent, and omitted on the main agent.prompt_idis added when the event belongs to a model turn whose prompt id is known.PostToolUseandPostToolUseFailurealso gainduration_ms, the time the tool spent executing, measured from the moment execution starts so that validation and approval time are excluded; it is omitted when a tool is cancelled before it starts. Both the core scheduler used by the terminal UI and headless runs and the ACP session report it.The three copies of the approval-mode to permission-mode mapping, in the core client, the agent tool and background agent resume, are replaced by one shared helper that the hook event handler also uses.
Why it's needed
Only some events carried
permission_mode, so a script shared across several events could not branch on it, andagent_idwas only present onSubagentStartandSubagentStopeven though the guide said subagent hooks receive it. The Claude Code hook contract puts these fields on every event's input, so scripts written against it read them unconditionally.duration_mson the post-tool events lets audit and metrics hooks record tool latency without timing it themselves.agent_typeis not added to the common input. The agent context available at fire time only tracks the agent id, and threading the type through every place that starts an agent is a separate change;SubagentStartandSubagentStopkeep reporting it.Reviewer Test Plan
How to verify
Unit tests:
cd packages/core && npx vitest run src/hooks/permission-mode.test.ts src/hooks/hookEventHandler.test.tscovers the mapping, the session approval mode on an event without its own mode, an explicit event mode winning,agent_idandprompt_idpresent only inside an agent context and a prompt context, andduration_mspresent only when given.npx vitest run src/hooks/hookSystem.test.ts src/core/toolHookTriggers.test.ts src/core/coreToolScheduler.test.ts src/core/client.test.ts src/tools/agent/agent.test.ts src/agents/background-agent-resume.test.ts src/goals/goalLoop.integration.test.tschecks the callers of the moved mapping and the new trigger parameter.End to end: a
PostToolUsehook that copies its stdin to a file, thenqwen -p "Run the shell command: sleep 1 && echo done" --approval-mode yolo. The captured input showspermission_mode: "yolo", aprompt_id, and aduration_msa little over 1000.Evidence (Before & After)
Both runs use the same isolated user settings with two capture hooks that copy their stdin to a file, one on
UserPromptSubmitand one onPostToolUsefor the shell tool. The fields below are picked out of the captured JSON;nullmeans the key was absent. The baseline is the installed release, which builds hook input likemain.qwen -p "Reply with the word ok." --approval-mode auto-edit, capturedUserPromptSubmitinput:qwen -p "Run the shell command: sleep 1 && echo done" --approval-mode yolo, capturedPostToolUseinput:agent_idstays absent on the main agent, as intended. Unit tests on this branch:permission-mode.test.ts,hookEventHandler.test.ts,hookSystem.test.tsandtoolHookTriggers.test.tspass;coreToolScheduler.test.ts,client.test.ts,agent.test.ts,background-agent-resume.test.tsandgoalLoop.integration.test.ts1143 passed; the hook tests inSession.test.ts82 passed. With the event handler change reverted, the four new common-input cases fail.Tested on
Environment (optional)
Local build of this branch (
npm run build && npm run bundle) with an isolatedQWEN_HOME.Risk & Scope
agent_typeon every event,duration_mson events other than the two post-tool events, and the ACP path end to end (covered by the shared trigger and by unit tests only).Linked Issues
Part of #11610
中文说明
这个 PR 做了什么
每个 hook 输入现在多带三个公共字段。
permission_mode是会话的审批模式;原本就上报自身适用模式的事件(工具事件、子代理事件、SessionStart)保留自己的值。事件在子代理内触发时会带上agent_id,主代理上则不带。事件属于一个已知 prompt id 的模型轮次时会带上prompt_id。PostToolUse和PostToolUseFailure还新增duration_ms,即工具执行耗时,从执行真正开始时计时,不包含校验和审批时间;工具在开始执行前被取消时不带该字段。终端 UI 与无头运行所用的 core 调度器,以及 ACP 会话都会上报它。审批模式到权限模式的映射原本在 core 客户端、agent 工具和后台 agent 恢复里各有一份,现在替换为一个共享帮助函数,hook 事件处理器也使用它。
为什么需要
以前只有部分事件携带
permission_mode,跨事件共用的脚本无法据此分流;agent_id也只出现在SubagentStart和SubagentStop上,尽管指南说子代理里的 hook 会收到它。Claude Code 的 hook 约定把这些字段放在每个事件的输入里,按该约定编写的脚本会无条件读取它们。post-tool 事件上的duration_ms让审计和指标类 hook 无需自行计时即可记录工具延迟。公共输入没有加
agent_type。触发时可用的 agent 上下文只记录了 agent id,把类型穿过所有启动 agent 的地方是另一项改动;SubagentStart和SubagentStop继续上报它。评审测试计划
如何验证
单元测试:
cd packages/core && npx vitest run src/hooks/permission-mode.test.ts src/hooks/hookEventHandler.test.ts:覆盖映射本身;没有自身模式的事件上报会话审批模式;事件显式上报的模式优先;agent_id和prompt_id只在 agent 上下文和 prompt 上下文中出现;duration_ms只在给定时出现。npx vitest run src/hooks/hookSystem.test.ts src/core/toolHookTriggers.test.ts src/core/coreToolScheduler.test.ts src/core/client.test.ts src/tools/agent/agent.test.ts src/agents/background-agent-resume.test.ts src/goals/goalLoop.integration.test.ts:检查被迁移映射的调用方以及新增的触发参数。端到端:配置一个把 stdin 复制到文件的
PostToolUsehook,然后运行qwen -p "Run the shell command: sleep 1 && echo done" --approval-mode yolo。捕获的输入里有permission_mode: "yolo"、prompt_id,以及略大于 1000 的duration_ms。证据(前后对比)
两次运行使用同一份隔离的用户设置,配置了两个把 stdin 复制到文件的捕获 hook,一个挂在
UserPromptSubmit,一个挂在 shell 工具的PostToolUse。下面的字段取自捕获到的 JSON,null表示该键不存在。基线是已安装的发行版,与main构造 hook 输入的方式相同。UserPromptSubmit(--approval-mode auto-edit):已安装的 0.22.3 上没有permission_mode和prompt_id;本分支上permission_mode为auto_edit,并带有当前轮的prompt_id。PostToolUse(sleep 1 && echo done,--approval-mode yolo):两者都有permission_mode: "yolo";本分支额外带有prompt_id,duration_ms为 1071。主代理上agent_id如预期不出现。完整对比见上文英文部分。本分支单元测试:
permission-mode.test.ts、hookEventHandler.test.ts、hookSystem.test.ts、toolHookTriggers.test.ts通过;coreToolScheduler.test.ts、client.test.ts、agent.test.ts、background-agent-resume.test.ts、goalLoop.integration.test.ts共 1143 个通过;Session.test.ts中的 hook 测试 82 个通过。撤回事件处理器的改动后,新增的四个公共输入用例失败。测试平台
仅在 Linux 上本地验证;macOS 和 Windows 未测试。
环境
本分支的本地构建(
npm run build && npm run bundle),使用隔离的QWEN_HOME。风险与范围
agent_type、两个 post-tool 事件以外事件上的duration_ms,以及 ACP 路径的端到端验证(仅由共享触发函数和单元测试覆盖)。关联 Issue
Part of #11610