fix(acp): Preserve submitted prompt provenance for auto recall - #11455
Conversation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
E2E verification reportVerified the submitted source in The global CLI and pre-fix daemon reproduced the missing-field failure: UserPromptSubmit ran with no After the fix, a real daemon invoked the packaged hook through an observing wrapper that passed its JSON input unchanged. No
Each run used four different session IDs. The tool scenario made three model requests but one hook invocation. Loopback provider request counts were 1/1/0/1. Cloud search HTTP calls were not independently intercepted, so the cloud result establishes hook invocation count and actual injected context, not a separately measured HTTP count. Holo used a fresh isolated scope and two synthetic records. Seed/read/delete verified exact ID, scope, and original content; both records ended with DELETE 200 followed by exact GET 404, with no residual IDs. Seed and deletion were direct Holo API calls for this hook-specific acceptance; the earlier explicit MCP write/delete acceptance is separate. B tests repository containment, not per-workspace profile routing. Temporary daemons, local servers, and configuration directories were cleaned up; no credentials or persistent user configuration were added. Validation: 869/869 session tests; root build, bundle, typecheck; focused lint; formatting and diff checks passed on macOS 26.6.2 / Node 22.22.3 / npm 10.9.8. Windows and Linux were not tested locally. 中文说明验证对应提交 全局 CLI 和修复前 daemon 均复现缺少字段的问题:UserPromptSubmit 执行但没有 修复后,真实 daemon 通过观察 wrapper 调用打包 hook,JSON 输入原样转发。未配置
每轮使用四个不同会话 ID。工具场景有三次模型请求,但只有一次 hook 执行。本地 provider 请求次数为 1/1/0/1。未独立拦截云端搜索 HTTP 请求,因此云端结果证明 hook 执行次数和实际注入上下文,不单独证明 HTTP 次数。 Holo 使用全新隔离 scope 和两条合成记录。创建、读取、删除时校验精确 ID、scope 和原文;两条记录最终均 DELETE 200 后精确 GET 404,无残留 ID。本轮针对 hook,直接调用 Holo API 创建和删除记录;此前的显式 MCP 写入和删除验收是独立测试。B 验证仓库包含关系,不验证按 workspace 路由 profile。临时 daemon、本地服务和配置目录均已清理;未添加凭证或持久用户配置。 验证:869/869 会话测试、全仓构建、打包、类型检查、定向 lint、格式和 diff 检查通过。环境为 macOS 26.6.2 / Node 22.22.3 / npm 10.9.8。未在本地测试 Windows 和 Linux。 |
|
Thanks for the rework — this is a materially different PR from the one I requested changes on, and the difference is the right one. Template looks good ✓ Problem: observed and reproduced, not theoretical. The design doc names the commit it was reproduced on: a normal daemon prompt on main Direction: aligned. Web Shell is a first-party client whose users can register Auto Recall and then silently get no retrieval — a real gap — and the fix expands no deployment contract (Direct Profile stays TTY-only, default MCP registration and provider protocols unchanged). Last run I escalated to @wenshao the question of whether ACP producer eligibility was a product decision already made; no human has ruled on it, but this rework answers it in the conservative direction — opt-in per request, no inference — and records the decision in a bilingual design doc. The upstream (claude-code) CHANGELOG has no direct reference to Size: cross-package ( Approach: the scope feels right and I found nothing to cut. Every edit traces to the stated goal: the 9 locale files are one translated string that had to move because its English source moved; the two integration READMEs describe the producer contract this PR changes; Risk: Stage 1e matched — Moving on to code review. 🔍 中文说明感谢这轮重写 —— 与我上次请求修改的那个 PR 已有实质差别,而且差别正是该有的方向。 模板完整 ✓ 问题: 已观测且已复现,不是理论性问题。设计文档写明了复现所在的 commit:main 方向: 对齐。Web Shell 是第一方客户端,其用户可以注册 Auto Recall 却静默地得不到任何检索 —— 这是一个真实缺口 —— 而本次修复没有扩大任何部署契约(Direct Profile 仍仅支持 TTY,默认 MCP 注册与 provider 协议不变)。上一轮我把「ACP 生产方资格是否是既定产品决策」升级给了 @wenshao;没有人对此作出裁定,但这次重写以保守方向回答了它 —— 逐请求显式启用、不做推断 —— 并把该决策记录在双语设计文档中。上游(claude-code)CHANGELOG 没有直接提到 规模: 跨包( 方案: 范围合理,我没有找到可以砍掉的部分。每处改动都能追溯到既定目标:9 个语言文件是一条翻译字符串,因其英文源串变动而必须跟随;两个 integration README 描述的正是本 PR 改动的生产方契约; 风险: Stage 1e 命中 —— 进入代码审查 🔍 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
Code reviewI wrote my proposal before opening the diff: the ACP path has no in-band signal separating "a human typed this" from "a machine composed this" — scheduled tasks, Live dispatches, sub-session spawns, retries and channel deliveries all funnel through the same The Critical I requested changes on is closed, and I verified that rather than taking it on faith. Three places I checked because they were where this could plausibly still go wrong:
Nothing rises to a blocker. Three non-blocking items, all completeness rather than correctness:
Public-contract note, not a defect. sequenceDiagram
participant P1 as Web Shell composer
participant P2 as REST prompt route
participant P3 as ACP bridge
participant P4 as ACP child admission
participant P5 as Session
participant P6 as UserPromptSubmit hook
participant P7 as Auto Recall provider
P1->>P1: capture original text before prepareSubmit
P1->>P2: prompt plus _meta qwen.submittedPrompt
P2->>P2: strip public and private keys from the forwarded body
P2->>P3: context.submittedPrompt, only with no channel, worker-auth or display marker
P3->>P3: strip both keys, re-inject the private key from trusted context
P3->>P4: _meta qwen.daemon.submittedPrompt
P4->>P4: trusted parent reads the private key only, direct client the public key only
P4->>P5: _meta qwen.daemon.submittedPrompt
P5->>P5: emit only on a fresh non-channel turn with a nonblank declaration
P5->>P6: prompt plus optional submitted_prompt
P6->>P7: one search, only when submitted_prompt is present
Internal dispatchers (scheduled tasks, Live tasks, sub-sessions, standalone sessions) enter at P3 with no context declaration and no Files changed (38 total — key paths)
Test evidence — the PR's own CIThis is an unattended CI run, so I built and executed nothing from this PR. The evidence below is the PR's own CI on the reviewed commit, read through the API, plus the completed sandboxed verification already posted in this thread. All five
Two automated probes on this head are worth reading alongside that table, because they bear directly on the finding I raised: Serve A/B built the PR base and this head, drove a fixed endpoint set against each and reported no response changes across 12 scenarios — consistent with a change that adds an opt-in field rather than altering existing responses. Web-shell Visuals reported no changed screenshot while naming three render-shaping files; by that check's own framing an empty result there is a coverage gap, not a clean bill of health. Sandboxed laneA That is the A/B I said last run I would want before dropping the finding, so I am dropping it. What it did not settle, and what I am therefore not claiming:
中文说明代码审查我在打开 diff 之前先写了自己的方案:ACP 路径上没有带内信号能区分「人输入的」与「机器组装的」—— 定时任务、Live 派发、子会话派生、重试与 channel 投递都汇入同一个 我上次请求修改的那条 Critical 已关闭,而且我是核实过的,不是采信。 我重点检查了三处最可能仍然出错的地方:
没有构成阻塞的问题。三项非阻塞事项,都属于完备性而非正确性:
公开契约说明,不是缺陷。 时序图与文件表见上方英文部分,此处不重复。内部派发器(定时任务、Live task、子会话、独立会话)在 P3 处进入,既无 context 声明也无 测试证据本次为无人值守 CI 运行,因此我没有构建或执行任何来自本 PR 的代码。下方证据是通过 API 读取的、本 PR 自身在被审 commit 上的 CI 结果,以及本线程中已完成的沙箱验证。 本 head 上五个 本 head 上有两项自动化探针与该表格值得一并阅读,因为它们直接关系到我提出的发现:Serve A/B 构建了 PR base 与本 head,对两者驱动固定端点集,报告 12 个场景无响应变化 —— 与「新增一个可选启用字段、而非改变既有响应」的变更相符。Web-shell Visuals 报告没有截图变化,同时点名了三个影响渲染的文件;按该检查自身的表述,那里的空结果是一个覆盖缺口,而不是健康状况良好的证明。 沙箱验证通道一次 这正是我上一轮说的、在撤回发现之前想要的那次 A/B,所以我撤回该发现。 它没有定论的部分,也就是我不主张的部分:
— Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
|
Confidence: 4/5 — the finding I blocked on is fixed, and fixed measurably; what keeps this off 5/5 is verification completeness, not doubt about the code. Going back to the proposal I wrote before reading the diff: it matched this PR on every load-bearing choice — declare positively, carry it as trusted metadata, never fall back to request text, never infer from a display projection. I did not find a simpler path it missed, and the two-key split I would have designed is the one the codebase already uses for What moved me from 2/5 last run is not that the author argued with the finding — it is that the fix inverted the polarity instead of extending the denylist. Last run I wrote that a denylist works only for the machine-driven producers someone remembered to enumerate, and that the class arriving with no The evidence bar moved with it. Last run I was explicit that my finding was read-verified rather than executed, and that I would drop it if a Reservations, all non-blocking and all named in Stage 2: the trusted-parent invariant the design doc promises is unpinned by a test; two guards are currently unreachable and their doc sentence should not be read as test-backed; nothing drove one request through every hop in a single run; and the multi-turn continuation claim plus the nine real daemon scenarios are the author's, on macOS, which I have attributed as claims throughout rather than counting as evidence. Windows and Linux never ran — CI skipped both, so ubuntu is the only unit line. None of that is a reason to hold the PR; it is a list of what nobody has observed yet. The one thing I cannot settle from the diff, tests and description is not technical. This PR widens a documented public contract: a new externally-suppliable meta key, and For the record on the review state: my earlier @doudouOUC — the ask from last run is met, and met in the stronger of the two ways it could have been. You inverted the gate instead of adding rows to it, and you kept the failing assertion as the acceptance criterion rather than rewriting it to pass. The PR body also discloses its own limits more carefully than the review asked for — the macOS-only coverage, the deferred design docs, and the historical Holo evidence explicitly marked as predating the correction. The three non-blocking items in Stage 2 are yours to take or leave; item 1 is the only one I would bother with, and it is a short mirror of a fixture you already wrote. @wenshao — this is the bot's vote, not the contract's. If you are fine with ACP/daemon clients opting into CI on this head is complete and green with nothing pending, so this run's approval is pinned to 中文说明Confidence: 4/5 —— 我此前阻塞的那条发现已修复,而且是可度量地修复;让它没到 5/5 的是验证的完备性,不是对代码本身的怀疑。 回到我在读 diff 之前写下的方案:它在每一个承重的选择上都与本 PR 一致 —— 正向声明、以可信元数据承载、绝不回退到请求文本、绝不从显示投影推断。我没有找到它遗漏的更简路径,而我本来会设计的双键拆分,正是本仓库对 让我从上一轮 2/5 改变判断的,不是作者与该发现争辩,而是这次修复反转了极性,而不是延长排除清单。上一轮我写过:排除清单只对有人记得枚举出来的机器驱动生产方有效,而那一类完全不带 证据的门槛也随之提高。上一轮我明确说过,我的发现是走查核实而非实跑核实的,如果一次 保留意见,均为非阻塞且都已在 Stage 2 中点名:设计文档承诺的可信父进程不变量没有测试钉住;两个守卫当前不可达,其文档句子不应被当作有测试支撑;没有任何一次运行把单个请求驱动过所有跳;以及多轮续轮结论与九项真实 daemon 场景属于作者、在 macOS 上完成 —— 我一律记为作者的结论,而非证据。Windows 与 Linux 从未运行 —— CI 跳过了两者,因此 ubuntu 是唯一的单元测试线。这些都不是扣住 PR 的理由;它们是一张「尚无人观测过什么」的清单。 我无法从 diff、测试与描述中定论的那一件事不是技术性的。本 PR 拓宽了一份成文公开契约:一个外部可提供的新元数据键,以及 关于评审状态的记录:我早先在 @doudouOUC —— 上一轮的要求已经达成,而且是以两种可能方式中更强的那一种达成的。你反转了闸门,而不是给它增加用例行;你把那条失败的断言保留为验收标准,而不是改写它让它通过。PR 正文对自身局限的披露也比评审所要求的更仔细 —— 仅 macOS 的覆盖、延期的设计文档,以及明确标注为早于本次修正的历史 Holo 证据。Stage 2 中三项非阻塞事项由你决定取舍;只有第 1 项我会去动,而它只是你已写好的一个 fixture 的简短镜像。 @wenshao —— 这是 bot 的一票,不是契约的一票。如果你认可 ACP/daemon 客户端逐请求启用 本 head 上的 CI 已完整且全绿、无待决项,所以本轮的批准钉在 — Qwen Code · qwen3.8-max-2026-09-02 Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 3c": I did not confirm whether a pre-existing test elsewhere in Session.test.ts pins that the hook does not fire at all on continue / restoreAskUserQuestion /runt…; "agent reverse-audit (round 1)": locating the daemon serve app's JSON body limit for the prompt route — only express.raw({ type: '*/*', limit: '8mb' }) on POST /session/:id/attachments ( se….
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 3c":I did not confirm whether a pre-existing test elsewhere in Session.test.ts pins that the hook does not fire at all on continue / restoreAskUserQuestion /runt…;"agent reverse-audit (round 1)":locating the daemon serve app's JSON body limit for the prompt route — only express.raw({ type: '*/*', limit: '8mb' }) on POST /session/:id/attachments ( se…。
— qwen3.8-max via Qwen Code /review (v0.23.1)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:
- R1-2 ACP producer contract assertions left unreconciled — already reported (comment 3967113105)
- R1-7 acceptance bullet claims continuation coverage no test provides — already reported (comment 3967113122)
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent 1c": did not confirm a shipped producer that sets qwen.daemon.promptDisplayText to a bare media placeholder for an inbound media-only message — the [image] / [aud…; "agent 1c": did not confirm end-to-end admission of a channel-wrapped prompt when channel-worker authorization or the sourceType === 'channel' check fails (the preconditi…; "agent 5": did not verify whether any in-repo channel adapter actually composes hidden context into text while leaving displayText unset — finding 3's trigger rests on…; "agent 5": mutation verdicts above are reading-based; I executed only the unmutated run (8 passed / 861 skipped) and made no edit in this shared worktree.; "agent 6c": did not execute packages/cli/src/acp-integration/session/Session.test.ts to confirm the new it.each block passes at HEAD — findings above rest on reading th….
Not reviewed: reverse audit — stopped before round 3 by the review time budget.
中文说明
仅完成部分审查,审查缺口已披露。
本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent 1c":did not confirm a shipped producer that sets qwen.daemon.promptDisplayText to a bare media placeholder for an inbound media-only message — the [image] / [aud…;"agent 1c":did not confirm end-to-end admission of a channel-wrapped prompt when channel-worker authorization or the sourceType === 'channel' check fails (the preconditi…;"agent 5":did not verify whether any in-repo channel adapter actually composes hidden context into text while leaving displayText unset — finding 3's trigger rests on…;"agent 5":mutation verdicts above are reading-based; I executed only the unmutated run (8 passed / 861 skipped) and made no edit in this shared worktree.;"agent 6c":did not execute packages/cli/src/acp-integration/session/Session.test.ts to confirm the new it.each block passes at HEAD — findings above rest on reading th…。
未审查:反向审计——评审时间预算不足,未能开始第 3 轮。
— qwen3.8-max via Qwen Code /review (v0.23.1)
|
@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 冲突,直到移除标签或达到轮次上限。移除 |
Channel turns are machine-relayed deliveries (loop jobs, webhook tasks, adapter-synthesized events), so nothing on them crossed a submission boundary. Since submitted_prompt presence alone gates Auto Recall's outbound provider search, emitting it there ran retrieval on unattended turns and could publish composed wrapper text as user provenance. Gate emission on a positive user-submission condition instead. Also reconcile the producer-set contract this PR changed: hooks.md, the Auto Recall READMEs, the in-product hook description with its locale mirrors, and the recording payload comment now state the actual rule, and the new design doc records the channel exclusion plus the strict-decoder rollout warning.
|
🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下: Address-review round — PR #11455Feedback dispositions1. R1-1 Critical —
|
|
@qwen-code /triage |
|
Sandboxed verification: ❌ not passed — findings reported (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: 55 passed · 0 failed · 55 total Flakiness gate: ✅ 2 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:55 通过 · 0 失败 · 55 总计 抖动门:✅ 2 changed test file(s) x 5 identical rounds, no divergence Verification reportverify-pr 11455 — fix(acp): Preserve submitted prompt provenance for auto recallVerdict: 中文摘要
Central claim and A/BCentral claim. A fresh ordinary ACP user submission supplies Control construction. The only behaviorally changed production file in the emission path is
7 of 14 discriminating cells flip absent→present; the 7 omission cells hold on both arms. Witness: Consumer-side A/B (wire oracle). The real emitted bytes were then fed to the real shipped hook bundle
Witness: FindingsF1 — Critical (new-surface): the gate is a denylist where the commit promises a positive condition, so unattended and model-authored cross-session dispatches publish provenance and fire retrievalThe second commit states: "Gate emission on a positive user-submission condition instead." The implementation ( Measured end-to-end (harness Reproducing commands: # producer: real Session code, no classifying marker, wrapper built by the real producer
cd packages/cli && npx vitest run src/acp-integration/session/Session.verifyprobe.test.ts \
-t "provenance" # artifact copy: tmp/pr11455-verify-*/Session.verifyprobe.test.ts
# consumer: real shipped hook bundle over a real loopback socket
node tmp/pr11455-verify-*/consumer-wire-oracle.mjs tmp/pr11455-verify-*/logs/probe-head.json headBlast radius (all dispatch through
What this is NOT (bounded, per the round's own measurements): not a security or DLP boundary — the design doc states the field "is not an authentication or DLP boundary"; not data loss; requires an administrator to have explicitly registered Auto Recall with credentials and repository binding; the wrapper text is admin-authored task text plus one fixed instruction sentence, and the sanitizer still redacts credential shapes; at base no ACP turn carried the field at all, so this is new surface created by this PR, not a regression of existing behaviour. The in-session cron/loop/notification drains ( Suggested direction (not applied, not measured this round — a policy decision for the author). Either honour the commit's stated polarity by requiring a positive trusted marker on the ACP path, or close the unattended class with the metadata that already exists: F2 — Suggestion: the new hooks.md sentence is falsified by the measured behaviour for "cron" and "teammate traffic"
F3 — Nice to have (awareness): realtime voice delegation is a model-relayed delivery treated as a user submission
F4 — Nice to have (doc reading, not measured as a defect): the recording paragraph's fallback clause is imprecise for ACP
Reviewer Test Plan, step by step
Not covered
MethodologyEnvironment: the CI verify container ( Flakiness gate logEvidence imagesHarness scripts and raw logs are in the workflow run artifacts (7-day retention). — Qwen Code · sandboxed verification |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Requesting changes on the exclusion gate added this round — one Critical survives it, and it is verified against 2b4f46ac, not inferred. Full chain, line by line, in the Stage 2 comment above.
The short version: isUserSubmissionTurn = isFreshUserTurn && !channelTurn closes the channel-classified producers (webhook tasks, /loop jobs, GitHub issue events) — that part I confirmed is correct and trustworthy, since bridge.ts:10502/:10530 strip the key from callers and re-arm it only for the authenticated channel-worker route at serve/routes/session.ts:6912.
But round 1 of the /review named a fourth population, and it is still open: daemon scheduled-task dispatch and Live task runs. Those call bridge.sendPrompt(sessionId, { sessionId, prompt: [...] }, undefined, { promptId, onPromptAdmitted }) — create-sub-session.ts:932-940, live-task-service.ts:1081-1092 — with a context that carries no channelPrompt and a request that carries no _meta at all. So channelTurn is false, every isFreshUserTurn exclusion term is false, isUserSubmissionTurn is true, and submittedPrompt falls back to promptText — the machine-composed task text — which is then emitted at Session.ts:5633-5640. create-sub-session.ts:7-14 documents both producers as the create_sub_session tool inside an agent turn and the isolated scheduled-task dispatch; neither is a human submission.
The consequence is the one the original Critical traced: auto-recall.ts gates an outbound provider search solely on a nonblank submitted_prompt, so an admin who registered Auto Recall on a daemon pays a search on every scheduled lap, every Live task dispatch and every sub-session spawn, with the result injected into that unattended turn's model input — and a hook written to the documented presence-implies-human pattern can block and kill the task at Session.ts:5641-5652.
Two reasons this is a code fix rather than a docs fix, and why I am not treating it as settled:
- Both sibling producers gate positively and this gate is now wider than either, which round 2 explicitly asked it not to be:
core/client.ts:3225-3230requiresmessageType === SendMessageType.UserQueryplus a producer-suppliedoptions.submittedPrompt;nonInteractiveCli.ts:2475-2477requiresisFirstTurn && sendType === SendMessageType.UserQuery. - The
hooks.mdsentence this PR rewrites still promises the field is omitted for "cron, notifications" — and anisolatedscheduled-task dispatch is the daemon's cron. Shipped doc contradicts shipped behaviour on a line this PR touched.
I ruled out the two explanations that would have made this moot: sub-sessions do not disable hooks (disableAllHooks has zero non-test hits under packages/cli/src/serve/ or packages/acp-bridge/src/), and sourceType === 'channel' would not rescue it either, because getChannelPromptDisplayText (bridge.ts:411-416) needs a string displayText regardless and channelPrompt — not sourceType — is what classifies the turn.
Suggested direction, same as round 1's: gate positively instead of subtracting. Either let the producer declare provenance through a trusted meta key admitted alongside DAEMON_PROMPT_DISPLAY_TEXT_META_KEY, or add a UserQuery-equivalent classification on the ACP path and require it. Acceptance criterion that actually pins it: a table row driving session.prompt() with an empty _meta — the shape those two call sites really send — asserting the hook input deep-equals { prompt: ... } with no submitted_prompt, while 'trusted display projection' keeps asserting submitted_prompt: 'original question'; then remove the gate and confirm the new row goes red. No current row has an empty _meta, which is why all ten pass in both directions.
To be explicit about what I am not asking for: the original capture point, the ??-not-|| choice, the projection parity with headless, the trust boundary, the retry handling and the locale mirroring are all correct and I verified each rather than assuming it. The refusal of the channelTurn ? '' : promptText ternary as dead defense was also right — submittedPrompt is read only inside the short-circuit at :5637/:5638. This is about the gate's shape, not the rest of the change.
Honest limits on this finding: it is a read-verified chain, not an executed probe — this gate does not run PR-derived code, so I have no runtime evidence of the field leaving the process on a scheduled-task dispatch. @qwen-code /verify would settle it with an A/B against the base build and a wire oracle on the hook stdin; if that comes back clean I will drop the finding. Also unresolved and escalated to @wenshao in the Stage 3 comment: whether ACP producer eligibility is a product decision already made, in which case this becomes a hooks.md correction instead of a gate change, and whether the two deferred design docs (submitted-prompt-provenance.md, direct-external-context-auto-recall.md, both still rowing ACP as Absent) should be reconciled in this PR or a follow-up.
中文说明
针对本轮新增的排除闸门请求修改 —— 有一条 Critical 在该闸门之后依然存在,且已在 2b4f46ac 上逐行核实,不是推断。完整链路见上方 Stage 2 评论。
简述:isUserSubmissionTurn = isFreshUserTurn && !channelTurn 关闭了 channel 分类的生产方(webhook 任务、/loop 任务、GitHub issue 事件)—— 这部分我确认是正确且可信的,因为 bridge.ts:10502/:10530 会剥离调用方传入的该键,仅在 serve/routes/session.ts:6912 的已认证 channel-worker 路由上重新注入。
但 /review 第 1 轮点名的第四类生产方仍然敞开:daemon 定时任务派发与 Live task 运行。它们调用 bridge.sendPrompt(sessionId, { sessionId, prompt: [...] }, undefined, { promptId, onPromptAdmitted }) —— create-sub-session.ts:932-940、live-task-service.ts:1081-1092 —— context 不带 channelPrompt,请求完全不带 _meta。于是 channelTurn 为 false,isFreshUserTurn 的每一项排除条件都为 false,isUserSubmissionTurn 为 true,submittedPrompt 回退到 promptText —— 机器组装的任务文本 —— 并在 Session.ts:5633-5640 被发出。create-sub-session.ts:7-14 明确记录这两类生产方是 agent 回合内部的 create_sub_session 工具与 isolated 定时任务派发;两者都不是人类提交。
后果正是原 Critical 追溯过的那一个:auto-recall.ts 仅凭非空 submitted_prompt 就决定是否发起对外 provider 检索,因此在 daemon 上注册了 Auto Recall 的管理员,每一次定时触发、每一次 Live task 派发、每一次子会话派生都会产生一次检索,结果还会注入该无人值守回合的模型输入 —— 而按「字段存在即代表人类」这一成文约定编写的 hook,可能在 Session.ts:5641-5652 返回 block 并终止该任务。
有两点使它属于代码修复而非文档修复,也是我不把它当作已定论的原因:
- 两个同级生产方都是正向判定,而本闸门现在比二者都宽 —— 这正是第 2 轮明确要求不得发生的:
core/client.ts:3225-3230要求messageType === SendMessageType.UserQuery且由 producer 提供options.submittedPrompt;nonInteractiveCli.ts:2475-2477要求isFirstTurn && sendType === SendMessageType.UserQuery。 - 本 PR 改写的那句
hooks.md仍承诺该字段对「cron、notifications」省略 —— 而isolated定时任务派发正是 daemon 的 cron。在本 PR 亲手改过的那一行上,已发布的文档与已发布的行为相互矛盾。
我排除了两种会使本发现失效的解释:子会话并未禁用 hook(disableAllHooks 在 packages/cli/src/serve/ 与 packages/acp-bridge/src/ 下的非测试代码中命中数为零);sourceType === 'channel' 也救不了它,因为 getChannelPromptDisplayText(bridge.ts:411-416)无论如何都需要字符串 displayText,而对回合做分类的是 channelPrompt,不是 sourceType。
建议方向与第 1 轮一致:以正向判定代替逐项排除。要么由 producer 通过一个与 DAEMON_PROMPT_DISPLAY_TEXT_META_KEY 同样只在受信父级放行的 meta 键来声明来源,要么在 ACP 路径上引入等价于 UserQuery 的分类并要求其成立。真正能钉住它的验收标准:补一行以空 _meta 驱动 session.prompt() 的用例 —— 即上述两处调用点实际发送的形状 —— 断言 hook 输入严格等于 { prompt: ... } 且不含 submitted_prompt,同时保留 'trusted display projection' 行断言 submitted_prompt: 'original question';随后移除该闸门,确认新用例变红。当前没有任何一行的 _meta 为空,这正是十行用例在两个方向上都通过的原因。
需要明确说明我没有要求什么:最初的捕获时机、?? 而非 || 的选择、与 headless 一致的投影构造、可信边界、重试处理以及语言镜像同步都是正确的,我逐项核实而非假定。把 channelTurn ? '' : promptText 三元表达式作为无效防御加以拒绝也是对的 —— submittedPrompt 只在 :5637/:5638 的短路之内被读取。本次意见针对的是闸门的形状,不是这次改动的其余部分。
本发现的诚实边界:它是代码走查得出的链路,不是实跑探针 —— 本闸门不执行 PR 衍生的代码,因此我没有运行时证据证明该字段会在一次定时任务派发中离开进程。@qwen-code /verify 可以通过针对基线构建的 A/B 与 hook stdin 上的线上 oracle 定论此事;如果结果是干净的,我会撤回该发现。另有一项未决事项已升级给 @wenshao(见 Stage 3 评论):ACP 生产方资格是否属于既定的产品决策 —— 若是,这就变成一次 hooks.md 更正而非闸门改动;以及两处延期的设计文档(submitted-prompt-provenance.md、direct-external-context-auto-recall.md,二者仍把 ACP 标记为 Absent)应在本 PR 还是后续 PR 中对齐。
— Qwen Code · qwen3.8-max-2026-09-02
chiga0
left a comment
There was a problem hiding this comment.
Scope: reviewed packages/cli/src/acp-integration/session/Session.ts (all changed hunks + surrounding context to ~5640), packages/cli/src/serve/create-sub-session.ts, packages/cli/src/serve/live/live-task-service.ts, packages/acp-bridge/src/bridgeTypes.ts, packages/cli/src/serve/routes/session.ts (meta-key stripping logic), packages/cli/src/ui/components/hooks/constants.ts, i18n locale patches (titles only), chatRecordingService.ts (comment update). Working tree unavailable — test execution not run.
Blocker
F1 — isUserSubmissionTurn does not exclude daemon sub-session and Live task dispatches
isUserSubmissionTurn = isFreshUserTurn && !channelTurn. Inside the hook-fire block, isFreshUserTurn reduces to !isRetry (the other three terms are already excluded by the outer if). channelTurn is set only for turns that arrive via the authenticated channel-worker route, which strips and re-injects the CHANNEL_PROMPT_META_KEY flag.
Two callers bypass this path entirely:
packages/cli/src/serve/create-sub-session.ts:932— callsbridge.sendPrompt(sessionId, { sessionId, prompt: [{ type: 'text', text: info.prompt }] }, undefined, { promptId, onPromptAdmitted })— nochannelPromptin options, no_metain the request.packages/cli/src/serve/live/live-task-service.ts:dispatchPrompt— callsbridge.sendPrompt(sessionId, { sessionId, prompt: [{ type: 'text', text: prompt }] }, undefined, { promptId, ... })— same shape, nochannelPrompt.
For both: isRetry = false, channelTurn = false, so isUserSubmissionTurn = true and submittedPrompt falls back to promptText (the machine-composed task text). submittedPrompt.trim().length > 0 is almost certainly true for a non-trivial task, so submitted_prompt is emitted to the hook.
Consequence: a daemon session with Auto Recall configured pays an outbound memory-provider search on every sub-session spawn and every Live task dispatch. Per the hook implementation, submitted_prompt presence is the only gate before the search fires; the result is then injected into that unattended machine turn's model input. A blocking hook written to the documented "presence implies human submission" pattern can also kill the automated task at the hook blocking-decision check.
Contrast with sibling producers:
core/client.ts— requiresmessageType === SendMessageType.UserQueryplus an explicitoptions.submittedPromptnonInteractiveCli.ts— requiresisFirstTurn && sendType === SendMessageType.UserQuery
Both gate on a UserQuery type. The ACP path has no equivalent; the channelTurn guard only covers channel-worker authenticated HTTP-originated turns, leaving internal bridge dispatches unchecked.
Verified against head 2b4f46ac by reading both callers. This finding was also filed as Critical R1-1 by the ci-bot across two review rounds; I confirm it independently.
Suggestions (non-blocking)
S1 (Session.ts:5299) — The ACP path is now a submitted_prompt producer, but cross-file contract assertions (e.g., docs/design/submitted-prompt-provenance.md, hooks.md producer table) were partially updated in this commit (hooks.md, both Auto Recall READMEs are updated ✓) but the predecessor design doc submitted-prompt-provenance.md rows ACP as Absent ("No producer added in this change"). The dev-bot confirms partial reconciliation; verify the predecessor design and any strict-decoder rollout documentation are fully updated.
S2 (docs/design/daemon-user-prompt-submit-provenance.md:30) — Acceptance criteria state "retry and continuation behavior" is unit-tested, but no test in the new it.each block drives a continue, isRestoreAskUserQuestion, or runtime-goal turn against a registered UserPromptSubmit hook — only legacy and daemon retries are covered. Continuation exclusions are exercised only implicitly by the outer if guard (not as explicit test cases).
Cross-check against existing reviews
- qwen-code-ci-bot R1-1 (Critical, three rounds): independently confirmed above —
create-sub-session.tsandlive-task-service.tsboth confirmed as callers that do not setchannelPrompt. Finding stands. - qwen-code-ci-bot R1-2 / R2-1 (Suggestion): docs partially reconciled in this commit (hooks.md, integration READMEs updated). Predecessor design doc check per S1 above.
- qwen-code-ci-bot R1-7 (Suggestion): continuation test gap confirmed per S2 above.
- qwen-code-dev-bot comment on R1-2: confirms partial doc reconciliation. The doc changes in this commit address the forward-looking producer description; the backward-compat/strict-decoder concern in R2-1 is still open.
Unreviewed dimensions
- Test execution at current head (working tree unavailable).
- Whether
isolatedscheduled-task dispatch also bypasseschannelTurn(same pattern expected based on the architecture, but not individually confirmed). - Integration Tests (CLI, No Sandbox) noted as skipped in CI.
No approval. Blocker: machine-dispatched sub-session and Live task turns incorrectly receive submitted_prompt, triggering Auto Recall on unattended automated turns.
Reviewed with AI assistance.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Partially reviewed — gaps disclosed.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 8)": whether channel-worker-supervisor.ts:1072 's opts.workspace and serve/routes/session.ts:6849 's runtime.workspaceCwd can differ by path normalization (sym…; "agent reverse-audit (round 2)": whether a Web Shell/daemon client actually resubmits rewind-restored or composer-history text through the prompt route — I confirmed POST /session/:id/rewind ….
Not reviewed: reverse audit — stopped before round 9 by the review time budget.
Deferred under the convergence posture (round 3, not a blocker) — recorded, not requested in this round:
packages/cli/src/acp-integration/session/Session.ts:5299 — [review] D3-1 third copy of the same projection expression in one closure (:5299, :5368, :5453) — deferred by the code-age rule, the line is unchanged since the previous round
Convergence: round 3 posted 9 inline comment(s), 8 of them reported for the first time; the previous round posted 2 (1 new). Findings keep coming back to the same files: docs/design/daemon-user-prompt-submit-provenance.md (findings in round 2; 1 more now); packages/cli/src/acp-integration/session/Session.ts (findings in round 1; 1 more now). The rate of new findings is not falling. A cluster that keeps producing siblings usually means the fixes are treating instances of a shared root cause — triaging that cause before the next round, or splitting an independent cluster into its own pull request, tends to end the loop faster than fixing them one at a time. Batching the remaining fixes and verifying them before the next push, or dropping this PR's reviews to --severity-floor critical, keeps the loop from re-deriving the same set. (Observation only — nothing was withheld from this review because of this observation.)
中文说明
仅完成部分审查,审查缺口已披露。
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 8)":whether channel-worker-supervisor.ts:1072 's opts.workspace and serve/routes/session.ts:6849 's runtime.workspaceCwd can differ by path normalization (sym…;"agent reverse-audit (round 2)":whether a Web Shell/daemon client actually resubmits rewind-restored or composer-history text through the prompt route — I confirmed POST /session/:id/rewind …。
未审查:反向审计——评审时间预算不足,未能开始第 9 轮。
收敛姿态下延后(第 3 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。
收敛情况:第 3 轮发布了 9 条行内评论,其中 8 条是首次提出;上一轮发布了 2 条(其中 1 条首次提出)。发现反复回到同一批文件:docs/design/daemon-user-prompt-submit-provenance.md(第 2 轮已出过发现,本轮又有 1 条);packages/cli/src/acp-integration/session/Session.ts(第 1 轮已出过发现,本轮又有 1 条)。新发现的产出速度没有下降。一个不断再生兄弟发现的簇,通常意味着逐条修复只在处理同一根因的实例——先定位并处理该根因,或把独立的簇拆成单独的 PR,通常比逐条修复更快结束循环。把剩余修复攒成一批、验证后再推送,或将本 PR 的评审降到 --severity-floor critical,可以避免循环反复推导同一组发现。(仅为观察——本轮评审未因此扣留任何内容。)
— qwen3.8-max via Qwen Code /review (v0.23.2)
|
🤖 AutoFix crashed before it could evaluate the feedback (attempt 2/100) — it will retry on the next scan. AutoFix failed before producing a verified commit (the run crashed or timed out before it could explain why). 中文说明🤖 AutoFix 在评估反馈之前崩溃(第 2/100 次尝试)—— 将在下次扫描时重试。 Run log: https://github.com/QwenLM/qwen-code/actions/runs/34441347168 🧠 Handled by Qwen Code · model/模型 |
Carry original Web Shell composer text separately through admission and queues; keep background dispatches and undeclared requests ineligible. Reconcile hook and integration contracts and add regression coverage. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Review corrections and verification for 2438732.
Verification report:
The temporary |
Incorporate main at 1097b9f so the PR is validated by the current lint gate. The provenance correction is preserved without manual conflict resolution. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Synchronized main in b4ee502 to address the new CI gate failure.
The temporary autofix pause is being removed and the hourly babysit task remains enabled. New-head CI results are pending. |
🖼️ web-shell visual previewRendered against a mock daemon (no real backend): the PR base vs this PR head Screenshots · before / afterℹ️ No screenshot changed against the PR base — but this PR edits 3 render-shaping files:
Either the change has no visual effect (logic, plumbing, a state the scenarios never reach), or no scenario renders this UI — in which case the preview cannot see it, and an empty result is a coverage gap rather than a clean bill of health. To make it visible, add a scenario to Full-resolution recordings (.webm) are attached to the workflow run. — Qwen Code · web-shell visuals |
🩺 serve daemon A/BBuilt the PR base vs this PR head ✅ No response changes against the PR base across 12 scenario(s). — Qwen Code · serve A/B |
…emon-auto-recall-submitted-prompt
|
Synchronized main in
The outstanding CHANGES_REQUESTED review still needs a fresh reviewer decision on this head — this sync does not dismiss it and makes no production behavior change. New-head CI results are pending. |
|
@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: 67 passed · 0 failed · 67 total Flakiness gate: 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:67 通过 · 0 失败 · 67 总计 抖动门: Verification reportverify-pr 11455 — fix(acp): Preserve submitted prompt provenance for auto recallVerdict: 中文摘要
Previous-finding status (round 1 → this head)Every row was re-measured at
No round-1 finding stands, worsened, or was declined. Central claim and A/BCentral claim. On the ACP/daemon path, Control construction. Three arms differing only in Probe = the PR's own 14-row provenance table plus 6 rows I added: the 5 F1 blast-radius shapes with text built by the real
Head 20/20 green; base red on exactly the 4 emission rows (feature absent — an intended control red, encoded as a passing assertion); denylist red on 11 rows including all 5 GAP rows. Witness: Consumer-side A/B (wire oracle). The bytes each arm's real
Search count tracked Mutation matrix (vacuity + guard completeness)14 single-expression mutants of the guards this PR adds, each run against the suite that should catch it, with the file restored and sha256-re-verified after every cell. All 5 unmutated controls green first. 11 killed / 3 survived. Every survivor has a killed control in its own file (M7 in
The design doc's own acceptance criterion — "Remove the new declaration gate and confirm omission cases fail" — is M5, and it fails 8 rows. The PR's new tests are not vacuous. FindingsAll three are completeness reporting, not merge conditions — none is load-bearing, and each is named with the pinned sibling that closes the same hazard today. N1 — Suggestion: a documented invariant about trusted parents is unpinned
I enumerated every trusted parent rather than inferring it, and the invariant is not currently violable: N2 — Nice to have: the bridge's
|
| plan step | result |
|---|---|
| "Verify declared user input and undeclared machine input independently" | verified — 20-row × 3-arm table; declared rows emit, all 5 undeclared machine rows absent, and the denylist control proves the rows discriminate |
| "host-prepared text, attachments, model-only context, and slash-command expansion never replace the original declaration" | verified at the Session/hook seam (text blocks without resource bodies keeps PRIVATE RESOURCE out; explicit submission overrides unrelated display text; model-only delegation excluded) and at the client seam by the PR's actions.test.ts / useQueuedPrompts.dom.test.tsx rows, which I ran (1280/1280) but did not independently mutate |
| "existing legacy hook invocation and recording behavior remain intact" | verified — 2009/2009 in the four packages/cli suites, 925/925 bridge, and prompt is still emitted on every row including all 16 absence rows |
| "Repeat without a declaration, with forged private metadata, and with invalid, blank, channel, or display metadata; no submitted field or provider search should occur" | verified — every one of those shapes is a probe row, and the consumer oracle shows 0 outbound searches for all 16 absence rows |
| "A real tool-result continuation must not repeat retrieval" | not verified — needs a multi-turn model loop (see Not covered) |
"In Web Shell, verify direct, host-prepared, queued, and converted /skills submissions preserve the original text" |
partially verified — by call-site census plus the PR's own green tests; I drove no real browser |
| "generic actions and manual scheduled runs must not declare it automatically" | verified by census — see the bounded note above |
| "real rebuilt daemon/REST/bridge/ACP-child/hook/provider chain passed nine scenarios" | not verifiable in this container — no credentials or network; the author's claim is untested here. My chain evidence is hop-by-hop, not end-to-end |
Not covered
- Real daemon end-to-end (the author's nine scenarios, Holo/Mem0 records, workspace A/B binding): requires provider credentials and network; this container has neither. The consumer oracle substitutes a loopback provider and says so.
- End-to-end chain across all hops. I verified each hop separately — REST route (
server.test.ts, M13/M14), ACP HTTP dispatch (transport.test.ts, M12), bridge (bridge.test.ts, M9/M10/M11), ACP child admission (acpAgent.test.ts, M6/M7/M8), Session emission (my 3-arm probe), hook consumer (real bundle + real socket) — but never drove one request through REST → bridge → a real spawned child → Session → hook in a single run. A hop-by-hop chain can hide a seam mismatch; this is the shape of the gap, not a claim that one exists. - Multi-turn behaviour: "a tool-result continuation does not repeat retrieval", Stop-hook re-entry, mid-turn steering. Read in code, not driven.
- Per-commit attribution: the checkout is depth 2 (
git rev-parse --is-shallow-repository= true).git rev-list HEAD^1..HEAD^2returns 1 commit while the snapshot'scommitsarray lists 5;4fb7d2f0,2b4f46ac,2438732aandb4ee5026are all unreachable (git cat-file -tfails on each). Only the aggregateHEAD^1..HEADdiff was exercised, so I cannot attribute the polarity inversion to2438732aby measurement — only by its headline. - Base OID drift: the snapshot's
baseRefOidis2488d12d…, which is unreachable locally; the merge ref'sHEAD^1is0ef3535…. Per the merge-ref contract I usedHEAD^1throughout. Mitigating fact: the baseSession.tsblob is byte-identical to round 1's (51fb91f6…), so the file under test did not move between the two base tips. - Trial merge into current
main: not performed. - Exhaustive
_meta-forwarding census. I enumerated the three ingresses the PR names and both trusted parents, and greppedpackages/channels/for the new keys (zero hits). I did not prove that no other code path in the repo forwards a caller-controlled_metato an ACP child. - Windows/Linux execution paths (author-declared untested); no real browser UI.
- No repo-wide gate: typecheck, lint, bundle and the unaffected workspaces were not run — the PR's own CI covers those and this round's budget went to the A/B.
Methodology
Environment: the CI verify container (node v22.23.2), merge-ref checkout at depth 2, npm ci + npm run build pre-run at HEAD. Producer arm (ab-driver.mjs): the PR's own Session.test.ts provenance table copied to a scratch Session.verifyprobe.test.ts with 6 added rows — 5 F1 blast-radius shapes whose text is built by the real buildScheduledTaskRunPrompt imported from src/runtime/scheduled-task-run.ts, plus one declared CONTROL row — and a messageBus seam that dumps every real hook input to JSONL. The driver swaps only Session.ts per arm (base = git show HEAD^1:…; denylist = head with one expression replaced by the previously verified head's polarity), sha256-verifies each swap and each restore, runs vitest with --reporter=json, and zips results positionally against the known row order because vitest renders it.each's $name quoted and truncated — the zip itself is validated per arm by asserting each rendered fragment is a prefix of the expected row name. Expected red cells are encoded as passing assertions, so fail counts only unexpected outcomes. Mutation arm (mutation-matrix.mjs): 14 single-expression mutants across 5 files in 3 packages, each preceded by an unmutated green control for its own runner, each restored and sha256-re-verified. Consumer arm (consumer-wire-oracle.mjs): spawns the shipped integrations/external-context/dist/auto-recall.js as a real child process, feeds it the exact dumped bytes with hook_event_name/prompt/submitted_prompt/cwd, points its generic-http-search-v1 provider at a loopback server recording method/path/auth/body, and attributes requests per spawn; nothing on the tested path is stubbed. Gates (run-gates.sh): the four affected workspace suites. The single input-closure shortcut taken: round 1's base blob was reused without re-deriving it, justified by the sha256 of HEAD^1:packages/cli/src/acp-integration/session/Session.ts being identical to the one round 1 quoted (51fb91f6…) — and everything that measurement consumes (the probe file, node_modules, config) was rebuilt or re-run this round regardless. Raw logs, JSONL dumps, harness sources and the three captures live in tmp/pr11455-verify-20260910-102405/ (logs/, evidence/, ab-driver.mjs, mutation-matrix.mjs, consumer-wire-oracle.mjs, run-gates.sh, plus the complete Session.verifyprobe.test.ts — copy it back to packages/cli/src/acp-integration/session/ to rerun either arm). The probe file was removed from the working tree after the runs: git status is clean and no scratch worktree remains.
Flakiness gate log
rounds=5 files=10 skipped=0
file packages/acp-bridge/src/bridge.test.ts: (cd packages/acp-bridge) npx --no-install vitest run ./src/bridge.test.ts
file packages/cli/src/acp-integration/acpAgent.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/acpAgent.test.ts
file packages/cli/src/acp-integration/session/Session.test.ts: (cd packages/cli) npx --no-install vitest run ./src/acp-integration/session/Session.test.ts
file packages/cli/src/serve/acp-http/transport.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/acp-http/transport.test.ts
file packages/cli/src/serve/server.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/server.test.ts
file packages/cli/src/ui/components/hooks/constants.test.ts: (cd packages/cli) npx --no-install vitest run ./src/ui/components/hooks/constants.test.ts
file packages/web-shell/client/App.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/App.test.tsx
file packages/web-shell/client/components/ChatPane.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/ChatPane.test.tsx
file packages/web-shell/client/daemon/session/actions.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/daemon/session/actions.test.ts
file packages/web-shell/client/hooks/useQueuedPrompts.dom.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/hooks/useQueuedPrompts.dom.test.tsx
per-file results (P=pass F=fail I=infra-exit, one letter per run):
packages/acp-bridge/src/bridge.test.ts: PPP
packages/cli/src/acp-integration/acpAgent.test.ts: PPP
packages/cli/src/acp-integration/session/Session.test.ts: PP
packages/cli/src/serve/acp-http/transport.test.ts: PP
packages/cli/src/serve/server.test.ts: PP
packages/cli/src/ui/components/hooks/constants.test.ts: PP
packages/web-shell/client/App.test.tsx: PP
packages/web-shell/client/components/ChatPane.test.tsx: PP
packages/web-shell/client/daemon/session/actions.test.ts: PP
packages/web-shell/client/hooks/useQueuedPrompts.dom.test.tsx: PP
verdict: timeout
summary: only 2 of 5 rounds fit the 15-minute budget; the completed rounds agreed
--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 1 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/acp-http/transport.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 1 · packages/cli/src/ui/components/hooks/constants.test.ts: P (exit 0)
round 1 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/ChatPane.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 1 · packages/web-shell/client/hooks/useQueuedPrompts.dom.test.tsx: P (exit 0)
round 2 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
round 2 · packages/cli/src/acp-integration/session/Session.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/acp-http/transport.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/server.test.ts: P (exit 0)
round 2 · packages/cli/src/ui/components/hooks/constants.test.ts: P (exit 0)
round 2 · packages/web-shell/client/App.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/ChatPane.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/daemon/session/actions.test.ts: P (exit 0)
round 2 · packages/web-shell/client/hooks/useQueuedPrompts.dom.test.tsx: P (exit 0)
round 3 · packages/acp-bridge/src/bridge.test.ts: P (exit 0)
round 3 · packages/cli/src/acp-integration/acpAgent.test.ts: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
|
@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: 144 passed · 0 failed · 144 total Flakiness gate: 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:144 通过 · 0 失败 · 144 总计 抖动门: Verification report<!-- qwen-triage:verify --> Sandboxed verification: ✅ passed — merge-ready (agent verdict) Ran the PR in an isolated, token-free container: A/B against the base build on the real production chain, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 144 passed · 0 failed · 144 total 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(真实生产链路上的 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:144 通过 · 0 失败 · 144 总计 Verification reportverify-pr 11455 (round 3) — fix(acp): Preserve submitted prompt provenance for auto recallVerdict: Follow-up round, zero code delta. 中文摘要
Previous-finding status (rounds 1–2 → this head)Every row was re-measured at
No round-1 or round-2 finding is worsened or declined. Central claim and A/BCentral claim. On the ACP/daemon path, This round's A/B runs on the real production chain, closing round 2's largest named gap. No code on the tested path is mocked: the bridge is the real
Both arms exit green (10/10 each) because each arm's expectations match its own build; the A/B is the pair of observed payloads. The control arm's patch is one expression in the compiled Core-path consumer oracle (round 2's unverified Test-Plan step). The core producer gate ( REST-hop chain on a real tokenless daemon. A background research pass established that
18/18 assertions. This closes the REST hop round 2 pinned only by mutation, end to end: REST route → CorrectionsRound 2's N1 evidence sentence does not reproduce as quoted. Round 2 stated that M6 — "turning the ternary into FindingsR3-1 — Suggestion (non-blocking, documentation): the rewritten hooks.md core-path sentence under-enumerates by one send typeThe PR rewrote the The omission predates this PR (the base sentence at Reproduce: grep -n 'Teammate` sends are skipped' docs/users/features/hooks.md
grep -n 'messageType !== SendMessageType.Goal' packages/core/src/core/client.tsSuggested fix (one word, doc-only): add " R3-2 — Nice to have (completeness, seam to watch): a second in-process entry into
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM — the Critical I requested changes on at 2b4f46a is fixed, and fixed by inverting the gate to a positive declaration rather than extending the denylist. This approval supersedes that review.
Verified at this head: all six bridge.sendPrompt call sites enumerated, and the four internal dispatchers (sub-session, Live task, Live session coordinator, standalone session) carry no context declaration and no _meta, so they cannot acquire provenance by construction. Both mint sites of the private-parent capability key counted independently. The full chain, three non-blocking items, and what remains unobserved are in the Stage 2 comment; my confidence and the contract sign-off I am leaving to a human are in Stage 3.
✅ Approving on the code. The widened public contract (a new externally-suppliable meta key, and submitted_prompt becoming reachable on ACP-path hook invocations) still deserves a maintainer's explicit yes — main wants two approvals.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not reviewed: test-efficacy probe — the mutation and hunk-necessity harness could not execute in this environment (harnessValidated: null, mutants not run, 46 hunks skippedForCap, 6 skippedForBaseline); a hand scan of every removed test line found no test weakened, disabled or deleted to let new behaviour through.
Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
Not explored to full depth (tool budget reached): "agent reverse-audit (round 2)": did not walk the daemon-side promotion route for a mid-turn insert ( enqueueMidTurnMessage in packages/cli / packages/core) to check the design doc's "promoted…; "agent reverse-audit (round 2)": whether the --input-file remote-input submit path passes options.submittedPrompt — the deleted hooks.md line explicitly listed "remote-input" as a non-produ….
Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.
17 Suggestion(s) were drafted inline past the resolved critical posting floor — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s); the CLI moved them into the deferral list below (floor enforcement).
Deferred under the convergence posture (round 4, not a blocker) — the floor engaged early: the first-time-finding rate has not fallen for 2 consecutive round(s) — recorded, not requested in this round:
docs/design/daemon-user-prompt-submit-provenance.md:35 — [review] R2-1: (fix-induced) The round-3 fix closed what was reported — the link now targets only the predecessor design's Compatibility and migration section for the strict-decoder n…docs/users/features/hooks.md:613 — [review] R4-1: The core/headless invocation list this sentence rewrites omits Goal sends, which client.ts also skips — and because the ACP clause added in the very same sentence explicitly names "runti…docs/users/features/hooks.md:624 — [review] R4-11: This rewritten eligibility paragraph leaves two paths misclassified, and both are fixable in one editing pass. First, "Retries … omit it" is not true of a caller-supplied retry flag on a …integrations/external-context-mem0/README.md:263 — [review] R4-14: This paragraph gains the ACP/daemon producer surface, but the third stale producer enumeration in this doc set — docs/design/external-context-mem0-auto-recall.md:108-113 , …packages/acp-bridge/src/bridge.ts:10541 — [review] R4-15: The !isPromotedMidTurn conjunct in this guard — which enforces a documented hard requirement — is asserted by no test, while both of its sibling conjuncts are covered by the test t…packages/acp-bridge/src/bridgeTypes.ts:977 — [review] R4-2: This new field is the only caller-populated member of BridgeClientRequestContext whose doc comment does not say where the value comes from, and its provenance is the inverse of e…packages/cli/src/acp-integration/acpAgent.ts:6109 — [review] R4-3: The trusted arm of this admission ternary — the hop the entire daemon and Web Shell production path depends on — is exercised by no test anywhere in the repo; only the unt…packages/cli/src/serve/acp-http/dispatch.ts:5875 — [review] R4-4: Two of this gate's suppression conjuncts — CHANNEL_PROMPT_META_KEY and DAEMON_PROMPT_DISPLAY_TEXT_META_KEY — have no test on the ACP transport, while the REST twin's equi…packages/cli/src/serve/routes/session.ts:6989 — [review] R4-13: The declaration is admitted on typeof alone here, with no length bound at any of the four admission or re-arm sites, while the sibling caller-supplied trusted string on this …packages/cli/src/serve/routes/session.ts:6992 — [review] R4-5: This provenance deny-list is hand-written twice — here over bound locals, and in packages/cli/src/serve/acp-http/dispatch.ts:5873-5878 over raw _meta lookups — with no share…packages/cli/src/ui/components/hooks/constants.ts:247 — [review] R4-12: This in-product /hooks description still asserts absolutely that prompt is "the current model-bound prompt", which this round's hooks.md rewrite makes false on th…packages/web-shell/client/daemon/session/actions.test.ts:29 — [review] R4-6: This new provenance test is nested inside describe('getConnectionAfterSessionClear', …) , a block whose subject is an unrelated pure function, while a describe('…packages/web-shell/client/daemon/session/actions.ts:1125 — [review] R4-7: The _meta merge that carries the declaration together with inputAnnotations in one object is untested at both rewritten sites, so the combined shape — every @ -m…packages/web-shell/client/daemon/session/types.ts:348 — [review] R4-10: One Web Shell user-submission boundary never declares provenance — the side-task initial prompt. packages/web-shell/client/components/artifacts/SideTaskPanel.tsx:304 …packages/web-shell/client/hooks/useQueuedPrompts.ts:624 — [review] R4-9: This new fail-closed reset — dropping a stored declaration when a server restore rewrites a summary-only queued row's payload — has no test, so it is a guard with no w…packages/web-shell/client/hooks/useQueuedPrompts.ts:1726 — [review] R4-16: The declaration is attached to the fallback row here and to the next-turn row at :2001 , but the mid-turn route that actually runs by default while the session is s…packages/web-shell/client/hooks/useQueuedPrompts.ts:1725 — [review] R4-8: This restoreAdmission carry of the declaration — the only thing that keeps provenance on the mid-turn-rejection requeue — is asserted by no test in the repo. Delete…
中文说明
未审查(原文为英文):test-efficacy probe — the mutation and hunk-necessity harness could not execute in this environment (harnessValidated: null, mutants not run, 46 hunks skippedForCap, 6 skippedForBaseline); a hand scan of every removed test line found no test weakened, disabled or deleted to let new behaviour through.
未审查(原文为英文):build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.
未探索到全部深度(达到工具调用预算):"agent reverse-audit (round 2)":did not walk the daemon-side promotion route for a mid-turn insert ( enqueueMidTurnMessage in packages/cli / packages/core) to check the design doc's "promoted…;"agent reverse-audit (round 2)":whether the --input-file remote-input submit path passes options.submittedPrompt — the deleted hooks.md line explicitly listed "remote-input" as a non-produ…。
未审查:反向审计——在 5 轮的反审轮数上限内未收敛。
17 条 Suggestion 在已解析的 critical 发布下限之外被起草为行内评论——发布下限因首次发现速率连续 2 轮未下降而提前生效;CLI 已将其移入下方延后清单(下限强制执行)。
收敛姿态下延后(第 4 轮,非阻断)——发布下限因首次发现速率连续 2 轮未下降而提前生效——已记录,本轮不要求修改:共 17 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.23.2)
Maintainer verification — real daemon, real hook, real browserI built the PR head Verdict: the mechanism does what the PR says, on every path I could reach. I found one hardening gap (unbounded declaration size) and one UX consequence worth a conscious decision before merge. Neither blocks. A/B over 16 prompt-admission casesAggregate: the That reproduces the reported problem (a configured Auto Recall profile is inert behind The admission gates all hold under adversarial input. A forged private S9 confirms the value is carried verbatim: Focused probesWeb Shell, driven in real headless Chromium against a live daemon. The browser genuinely puts Direct Genuine armed retry. This one needed staging to test honestly. A Tool-result continuation. S8 ran a real Leakage. Neither Finding — the declaration is unbounded
Measured: 64 KiB, 1 MiB, 4 MiB and 8 MiB declarations are all accepted with This is authenticated, so it is not an unauthenticated DoS, and the bundled Auto Recall hook caps its own stdin at 1 MiB and bails — so nothing shipped breaks. But the PR's own risk note says "eligible text reaches every configured hook", and a third-party hook that does not bound its stdin inherits this. A Behaviour worth a conscious decision, not a bugTyping into the Web Shell composer while a turn is running does not go through the prompt queue — the client sends it to For the same reason, the "queue" row I could reach over REST (S15) is a second prompt queued behind a running turn, which is a genuine fresh user turn and correctly does carry the declaration. Docs claim I checked because it was newly added"A blocking result stops the affected invocation, including on these non-TUI paths." Confirmed: a hook returning Scope I did not coverReal cron/Live dispatcher execution, channel-worker restart races, per-workspace profile routing, and Windows. The exclusion of scheduled tasks, sub-session spawns and model-authored Live tasks is guaranteed structurally here — the field is opt-in per request, so a producer that does not declare cannot acquire it — but I did not exercise those producers. Two unrelated environment notes so nobody chases them: RecommendationApprove. The chain is real, the gates hold under forgery and malformed input, the invocation behaviour of existing hooks is byte-identical to merge-base, and the A/B is unambiguous about what the PR adds. I'd like the size bound as a follow-up and one doc sentence about the mid-turn path. 中文说明维护者验证 —— 真实 daemon、真实 hook、真实浏览器我在 Linux(PR 标注为未测试的平台)上分别构建了 PR head 结论:机制在我能触达的每条路径上都与 PR 描述一致。发现一个加固缺口(声明长度无上限)和一个值得合并前明确取舍的 UX 后果,两者都不构成阻塞。 16 个准入场景的 A/B聚合结果: 这同时复现了所报告的问题(在 S1 这个与已发布客户端完全一致的形态下,已配置的 Auto Recall profile 在 各准入门禁在对抗性输入下都成立:伪造私有键 S9 证实取值是逐字携带的: 专项探针Web Shell(真实无头 Chromium 驱动实时 daemon)。 浏览器确实在 直连 真正被 arm 的 retry。 这一项需要认真构造才能诚实地测。对一个没有可重试内容的会话发 工具结果续轮。 S8 跑了真实的 泄漏检查。 捕获到的出站模型请求中, 发现 —— 声明长度无上限
实测:64 KiB、1 MiB、4 MiB、8 MiB 的声明全部以 这是需要鉴权的,因此不构成未授权 DoS;随仓库发布的 Auto Recall hook 也自行在 1 MiB 处截断并放弃,所以已发布的东西不会坏。但 PR 自己的风险说明写着"符合条件的文本会传递给每个已配置 hook",而不对 stdin 设限的第三方 hook 会继承这个问题。在两个路由入口加一个对齐相邻常量的 值得明确取舍、但不是 bug 的行为在一轮正在运行时往 Web Shell 输入框打字,走的不是提示词队列 —— 客户端发到 同理,我通过 REST 能触达的"队列"那一行(S15)是排在运行中轮次后面的第二条提示词,它是真正的新用户轮次,正确地携带了声明。 因为是新增而特意核对的文档主张"A blocking result stops the affected invocation, including on these non-TUI paths."已确认:一个基于 我没有覆盖的范围真实 cron/Live 派发器执行、channel-worker 重启竞争、按 workspace 路由 profile,以及 Windows。定时任务、子会话派生和模型撰写的 Live task 的排除在这里是结构性保证的 —— 字段是逐请求 opt-in,不声明的生产者无法获得它 —— 但我没有实际驱动这些生产者。 两条与本 PR 无关的环境情况,避免有人去追:我这台机器上 建议同意合并。链路是真实的,门禁在伪造与畸形输入下都成立,既有 hook 的调用行为与 merge-base 完全一致,A/B 对"这个 PR 增加了什么"给出了明确答案。希望把长度上限作为后续项补上,并为 mid-turn 路径补一句文档。 |
|
Released in v0.23.4. |














What this PR does
Carries original submitted text to UserPromptSubmit hooks through an explicit per-request declaration, enabling configured Auto Recall for Web Shell user submissions and opted-in ACP/daemon clients. Web Shell captures the original composer text before host preparation, slash-command rewriting, and attachment expansion, and preserves it separately in ordinary queues. Background dispatches, retries, channel deliveries, and requests without a valid declaration do not publish
submitted_prompt.Why it's needed
Released daemon sessions invoke the hook without
submitted_prompt, so provenance-gated Auto Recall skips retrieval. Inferring this field from every fresh non-channel request would also trigger retrieval for scheduled tasks, sub-session spawns, and model-authored Live tasks. Explicit declarations close that gap without inferring user provenance when a channel marker is removed or a display projection is available.Reviewer Test Plan
Verify declared user input and undeclared machine input independently. Confirm that host-prepared text, attachments, model-only context, and slash-command expansion never replace the original declaration. Check that existing legacy hook invocation and recording behavior remain intact.
How to verify
Configure the existing Auto Recall profile in an isolated launcher with a loopback provider and observing hook. Submit a declared original question with different request text; the hook must preserve the declaration, search once, and add the returned context to the model input. Repeat without a declaration, with forged private metadata, and with invalid, blank, channel, or display metadata; no submitted field or provider search should occur. A real tool-result continuation must not repeat retrieval. In Web Shell, verify direct, host-prepared, queued, and converted
/skillssubmissions preserve the original text; generic actions and manual scheduled runs must not declare it automatically.Validation: real rebuilt daemon/REST/bridge/ACP-child/hook/provider chain passed nine scenarios, and the original failing Session assertion passed unchanged. Affected bridge, ACP agent, Session, REST server, ACP HTTP, Web Shell action, App, ChatPane, and queue tests passed. Root build, bundle, typecheck, lint, and changed-file formatting passed. Independent code review and two self-audit passes found no remaining substantive issue. After merging main at
1097b9fe0ato refresh the CI gate, root build, bundle, typecheck, lint, formatting, all 1,383 Web Shell tests in the five affected suites, and 50 targeted backend tests passed again. The same nine real daemon scenarios also passed again on the clean merged headb4ee50261e.Evidence (Before & After)
N/A for visual changes; this PR changes hook behavior. Before correction at
2b4f46a, the reproduction asserted omission but receivedsubmitted_promptfor an undeclared machine request. After correction, that assertion passes; the real daemon probe reports no search for undeclared or invalid input, and one search for explicitly declared input. A realread_filecontinuation produced three model requests with one hook invocation and one search. The earlier four real Holo scenarios predate this correction and are historical evidence only.Tested on
Environment (optional)
macOS 26.6.2, Node.js 22.22.3, npm 10.9.8. Rebuilt local daemon and packaged hook with a controlled model and loopback provider; no cloud credentials were used in this correction. Build used an 8 GiB Node heap. Browser component tests used jsdom with Node experimental Web Storage disabled to avoid a local storage-global conflict.
Risk & Scope
_meta: { "qwen.submittedPrompt": "original submitted text" }; ordinary SDK transports must not add it globally. Existing clients without the optional declaration retain legacy hook behavior and skip provenance-gated recall. The private daemon-to-child key cannot be supplied by external callers. Default MCP registration and provider protocols are unchanged. Strict hook decoders must allow or explicitly handle the optionalsubmitted_promptfield.Design: English · 简体中文. Both versions have matching decisions, constraints, acceptance criteria, and evidence boundaries.
Linked Issues
No issue is closed by this PR. Follow-up validation after #11397 exposed this independent ACP hook gap.
中文说明
本 PR 做了什么
通过逐请求显式声明,将原始提交文本传递给 UserPromptSubmit hook,让 Web Shell 用户提交及显式启用的 ACP/daemon 客户端能够使用已配置的 Auto Recall。Web Shell 在宿主准备、斜杠命令改写和附件展开之前捕获原始输入框文本,并在普通队列中独立保存。后台派发、重试、channel 投递以及缺少有效声明的请求不发布
submitted_prompt。为什么需要
已发布的 daemon 会话调用 hook 时不携带
submitted_prompt,依赖来源信息的 Auto Recall 因此跳过检索。如果从每个全新非 channel 请求推断该字段,定时任务、子会话派生和模型撰写的 Live task 也会触发检索。显式声明修复这一缺口,并避免在 channel 标记被删除或存在显示投影时推断用户来源。审阅者测试计划
分别验证有声明的用户输入和无声明的机器输入。确认宿主准备后的文本、附件、模型专用上下文和斜杠命令展开不会替换原始声明。检查既有旧 hook 调用和录制行为保持不变。
如何验证
在隔离启动环境中配置已有 Auto Recall profile、本地 provider 和观察 hook。声明原始问题,同时使用不同的请求文本;hook 必须保留声明、搜索一次,并将返回上下文加入模型输入。分别测试无声明、伪造私有元数据、非法值、空白值、channel 或显示元数据;不得出现提交字段或 provider 搜索。真实工具结果续轮不得重复检索。在 Web Shell 验证直接提交、宿主准备、排队和转换后的
/skills提交均保留原文;通用 action 和定时任务手动运行不得自动声明。验证:真实重新构建的 daemon/REST/bridge/ACP 子进程/hook/provider 链路九项场景通过,原先失败的 Session 断言保持原期望并通过。相关 bridge、ACP agent、Session、REST server、ACP HTTP、Web Shell action、App、ChatPane 和队列测试通过。全仓构建、打包、类型检查、lint 及修改文件格式检查通过。独立代码审查和两轮自审未发现剩余实质问题。合入
1097b9fe0a处的 main 以更新 CI 门禁后,全仓构建、打包、类型检查、lint、格式检查、五个相关 Web Shell 测试文件的全部 1,383 项测试及 50 项后端定向测试再次通过。同样的九项真实 daemon 场景也在工作树干净的合并提交b4ee50261e上再次通过。证据(前后对比)
视觉变化为 N/A;本 PR 改变 hook 行为。修正前的
2b4f46a上,复现断言要求省略字段,却收到无声明机器请求的submitted_prompt。修正后该断言通过;真实 daemon 探针在无声明或非法输入时不搜索,显式声明输入时搜索一次。真实read_file续轮产生三次模型请求,仅调用一次 hook 和搜索。此前四项真实 Holo 场景早于本次修正,仅作为历史证据。测试平台
环境(可选)
macOS 26.6.2、Node.js 22.22.3、npm 10.9.8。重新构建的本地 daemon 和打包 hook,配合受控模型及本地 provider;本次修正未使用云凭证。构建使用 8 GiB Node 堆。浏览器组件测试使用 jsdom,并关闭 Node 实验性 Web Storage,以避开本机存储全局变量冲突。
风险与范围
_meta: { "qwen.submittedPrompt": "original submitted text" }在符合条件的请求上逐次启用;普通 SDK 传输层不得全局添加它。缺少这个可选声明的现有客户端保留旧 hook 行为,并跳过依赖来源的召回。外部调用方不能提供 daemon 到子进程的私有键。默认 MCP 注册和 provider 协议不变。严格 hook 解码器必须允许或显式处理可选submitted_prompt字段。设计:English · 简体中文。两个版本的决策、约束、验收标准及证据边界一致。
关联 Issue
本 PR 不关闭任何 issue。在 #11397 后续验证中发现了这个独立的 ACP hook 缺口。