Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1b83ebb
fix(core): separate hook context from transcript display
destire-mio Jul 28, 2026
a5d71df
Merge upstream main into codex/fix-7940-user-prompt-context
destire-mio Jul 29, 2026
eca5832
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Jul 29, 2026
10283d9
test(ci): gate desktop transcript projection
destire-mio Jul 29, 2026
0f86770
revert: keep desktop CI scope unchanged
destire-mio Jul 29, 2026
b9eaf55
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Jul 29, 2026
762ba8b
test: cover transcript display fallbacks
destire-mio Jul 29, 2026
ee7ee23
fix(transcript): address review feedback
destire-mio Jul 30, 2026
3879b70
chore: merge origin/main resolving UserPromptSubmit transcript-displa…
qwen-code-dev-bot Jul 31, 2026
00d06c8
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Jul 31, 2026
3052b25
fix(transcript): reconcile post-merge provenance paths
destire-mio Jul 31, 2026
f45208d
chore: merge origin/main into codex/fix-7940-user-prompt-context
qwen-code-dev-bot Aug 1, 2026
eab31c8
fix(webui): preserve legacy transcript concatenation
destire-mio Aug 1, 2026
5e80f24
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Aug 1, 2026
aa66dc1
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Aug 1, 2026
891ee3c
test(transcript): cover projection consumers
destire-mio Aug 2, 2026
8920e28
fix(transcript): consolidate hook context projection
destire-mio Aug 2, 2026
d808b4f
fix(transcript): support single-field display provenance
destire-mio Aug 2, 2026
ad0d857
fix(transcript): strip hook context with invalid metadata
destire-mio Aug 2, 2026
f90c51d
test(acp): cover empty replay display text
destire-mio Aug 3, 2026
a4be4f6
Merge branch 'main' into codex/fix-7940-user-prompt-context
wenshao Aug 3, 2026
d66130b
Merge origin/main into codex/fix-7940-user-prompt-context
destire-mio Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions docs/users/features/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,56 @@ When both fields are present, prompt-hook payloads contain overlapping text and

Sequential UserPromptSubmit hooks can append `additionalContext` to `prompt`; `submitted_prompt` continues to represent the captured submission. Function hooks are trusted same-process code and are not constrained by an immutability guarantee.

When the final hook output contains non-empty `additionalContext`, Qwen first
sanitizes the value and then sends it to the model as a separate text part:

```xml
<qwen:user-prompt-submit-context>
sanitized hook context
</qwen:user-prompt-submit-context>
```

The tag tells the model and transcript consumers that the part came from a
configured hook rather than from the user prompt. It is a provenance marker,
not authentication, authorization, or a general trust boundary.

For a `UserQuery` with this added context, the session JSONL record preserves
the model-bound parts, including the tagged part, and adds the following
`systemPayload`:

```json
{
"displayText": "pre-hook display projection",
"hookContext": "sanitized hook context"
}
```

This two-field payload is written only for this kind of user-prompt record.
`hookContext` intentionally duplicates the tagged part so offline and
third-party consumers can identify its provenance without parsing model text.
`displayText` is the pre-hook display projection and never includes the hook
context. For a supported interactive TUI submission it is the raw composer
projection carried by `submitted_prompt`; ACP, headless, `serve`, SDK, remote
input, and other paths without that provenance record the expanded pre-hook
prompt instead.
Comment on lines +666 to +668

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This overclaims the ACP/serve/SDK record shape. The spec says every UserQuery with added hook context preserves the tagged part and adds {displayText, hookContext}, but ACP-session turns record the raw pre-expansion input BEFORE the hook fires (Session.ts:3178, the only recordUserMessage call in that path) and send through GeminiChat.sendMessageStream directly, bypassing GeminiClient.sendMessageStream — the only place userPromptRecordPayload is written. Verified at the reviewed commit: headless genuinely matches the claim, TUI matches via submitted_prompt, but an ACP/serve/SDK JSONL user record has neither systemPayload nor tagged part. — Failure scenario: in a serve/ACP/SDK session whose hook injects additionalContext, the model sees the hook context but the JSONL record holds only the raw input text, while this spec tells offline/third-party consumers both are present — a consumer built on the spec finds no provenance on ACP/serve-originated sessions and misattributes or discards those turns. — Suggested fix: scope the sentences to the surfaces that write the payload, or extend the ACP path to write it.

Suggested change
projection carried by `submitted_prompt`; ACP, headless, `serve`, SDK, remote
input, and other paths without that provenance record the expanded pre-hook
prompt instead.
projection carried by `submitted_prompt`, and headless runs record the
expanded pre-hook prompt the same way. ACP, `serve`, and SDK turns record the
raw pre-hook input before the hook fires and carry no display provenance.
中文说明

此处对 ACP/serve/SDK 的记录形状表述过度。规范声称每个带 Hook 上下文的 UserQuery 都会保留标签 part 并附加 {displayText, hookContext},但 ACP 会话轮次在 Hook 触发之前就已记录原始未展开输入(Session.ts:3178,该路径唯一的 recordUserMessage 调用),且直接经 GeminiChat.sendMessageStream 发送,绕过了 GeminiClient.sendMessageStream——写入 userPromptRecordPayload 的唯一位置。已在被审提交上核实:headless 与描述相符,TUI 通过 submitted_prompt 相符,但 ACP/serve/SDK 的 JSONL 用户记录既无 systemPayload 也无标签 part。— 失败场景:在 Hook 注入 additionalContext 的 serve/ACP/SDK 会话中,模型看到了 Hook 上下文,但 JSONL 记录只有原始输入文本,而本规范告诉离线/第三方消费者两者都存在——按规范实现的消费者在 ACP/serve 来源的会话中找不到任何来源信息,会错误归因或直接丢弃这些轮次。— 建议修复:把表述限定到实际写入 payload 的界面,或扩展 ACP 路径使其写入 payload(见上方 suggestion,为前一种方案)。

— qwen3.8-max via Qwen Code /review (v0.21.7)


Transcript display consumers treat `displayText` as this user-prompt projection
when `systemPayload.hookContext` is a string. For compatibility with released
`displayText`-only user-prompt records, a complete tagged context in the final
part after at least one other part is equivalent pairing evidence. Notification,
cron, and mid-turn records can also have `displayText`, but those values are
compact display labels and must not be substituted for their model-bound text
without that evidence.
Comment on lines +674 to +676

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This sentence is ambiguous and contradicts first-party behavior: notification/cron/mid-turn records can never carry the pairing evidence defined above (hooks never fire for Cron/Notification — verified in client.ts's hook gate), yet ACP replay and CLI resume substitute their displayText label unconditionally, while webui, insight DataProcessor, and session-reference keep model text. "Substitute X for Y" is also a classically inverted construction, so both readings are available to an implementer. — Failure scenario: a third-party/offline consumer (this block's stated audience) implementing the rule as written never sees evidence on cron/notification records, so it renders their model-bound parts — the raw <task-notification /> envelope or the full internal cron prompt — where first-party surfaces show the compact label; the opposite reading contradicts webui's policy. Either way, following the spec diverges from at least one first-party surface. — Suggested fix: separate the two regimes.

Suggested change
cron, and mid-turn records can also have `displayText`, but those values are
compact display labels and must not be substituted for their model-bound text
without that evidence.
cron, and mid-turn records can also have `displayText`, but those values are
compact display labels. Subtype-aware consumers render those records using the
display label; the pairing-evidence rule above governs only user-prompt records.
中文说明

该句存在歧义且与第一方行为矛盾:notification/cron/mid-turn 记录永远不可能携带上文定义的配对证据(client.ts 的 Hook 门控已核实 Cron/Notification 从不触发 Hook),但 ACP replay 与 CLI resume 会无条件用 displayText 标签替换正文,而 webui、insight DataProcessor、session-reference 保留模型正文。"substitute X for Y" 又是经典的可反向解读结构,实现者两种读法都成立。— 失败场景:按本段目标读者(第三方/离线消费者)照字面实现规则时,cron/notification 记录永远没有证据,于是渲染其模型正文——原始 <task-notification /> 封装或完整 cron 内部 prompt——而第一方界面显示的是紧凑标签;反向读法又与 webui 策略矛盾。无论哪种读法,照规范实现都会与至少一个第一方界面不一致。— 建议修复:把两种机制分开表述(见上方 suggestion)。

— qwen3.8-max via Qwen Code /review (v0.21.7)

Legacy bare-context records keep their model-bound display behavior because the
context cannot be separated reliably. For metadata-free records that use the
current tagged shape, compatibility consumers may remove the same complete
final tagged part; they must not infer that arbitrary tag-like user text is hook
provenance.

Sensitive prompt telemetry attributes, when enabled, and managed auto-memory
recall both use the pre-hook prompt. They do not include
`UserPromptSubmit`-added context.

**Output Options**:

- `decision`: "allow", "deny", "block", or "ask"
Expand Down
Loading
Loading