docs(hooks): correct fold description + drop history-narrating test comments
Codex convergence findings on the delegate-and-fold fix (code path verified
correct, prose only):
- The hook-bridges RFC claimed a downstream `block` "carries the bridge context
too" for BOTH seams. True for `tools/post-execute` (PostToolDecision.block has
an additionalContext field) but false for `agent/prompt-submit`
(PromptDecision.block is `{kind,reason}` with no context field). The code is
already correct — a blocked prompt drops the context, which is right since the
prompt never reaches the model. Reworded the RFC to state the per-seam
difference accurately.
- Two test comments narrated "Before the fix…", which the current-state-only
doc rule forbids. Reworded to describe the behavior, not its history.
- Documented on concatContext (both bridges) why the merged block carries a
single source: a HookContext holds one MessageSource and the seam cannot
represent mixed provenance; rendering distinguishes only by source.kind, so a
downstream plugin's text stays framed as plugin context.
This commit is contained in:
@@ -214,7 +214,12 @@ export function apply(ctx: Context, config: Config): void {
|
||||
/**
|
||||
* Concatenate this bridge's {@link HookContext} (`ours`, always present at the
|
||||
* call sites) with a downstream listener's optional one, so folding our
|
||||
* additionalContext onto a delegated decision drops neither.
|
||||
* additionalContext onto a delegated decision drops neither. The merged block
|
||||
* carries a single `source` — this bridge's — because a `HookContext` holds one
|
||||
* `MessageSource` and the seam cannot represent mixed provenance; the rendered
|
||||
* `context/message` only distinguishes by `source.kind` ('plugin'), so a
|
||||
* downstream plugin's text is still correctly framed as plugin context, not a
|
||||
* user prompt.
|
||||
*/
|
||||
function concatContext(ours: HookContext, theirs: HookContext | undefined): HookContext {
|
||||
if (!theirs) return ours
|
||||
|
||||
@@ -433,7 +433,7 @@ describe('hooks-claude coverage — continue:false, context arm, no-cwd', () =>
|
||||
it('a context-only UserPromptSubmit hook DELEGATES so a later listener can still block', async () => {
|
||||
// A hook that only adds context must NOT short-circuit the waterfall: a
|
||||
// downstream agent/prompt-submit listener (a policy plugin) must still get to
|
||||
// block the prompt. Before the fix the bridge returned `allow` without next().
|
||||
// block the prompt. The bridge delegates via next() and folds its context.
|
||||
const d = dir()
|
||||
const s = sh(d, 'ctx.sh', '#!/usr/bin/env bash\necho \'{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"bridge ctx"}}\'\n')
|
||||
const path = hooks(d, { UserPromptSubmit: [{ hooks: [{ type: 'command', command: s }] }] })
|
||||
|
||||
@@ -169,7 +169,11 @@ export function apply(ctx: Context, config: Config): void {
|
||||
/**
|
||||
* Concatenate this bridge's {@link HookContext} (`ours`, always present at the
|
||||
* call sites) with a downstream listener's optional one, so folding our
|
||||
* additionalContext onto a delegated decision drops neither.
|
||||
* additionalContext onto a delegated decision drops neither. The merged block
|
||||
* carries a single `source` — this bridge's — because a `HookContext` holds one
|
||||
* `MessageSource` and the seam cannot represent mixed provenance; the rendered
|
||||
* `context/message` only distinguishes by `source.kind` ('plugin'), so a
|
||||
* downstream plugin's text is still correctly framed as plugin context.
|
||||
*/
|
||||
function concatContext(ours: HookContext, theirs: HookContext | undefined): HookContext {
|
||||
if (!theirs) return ours
|
||||
|
||||
@@ -71,8 +71,8 @@ describe('hooks-codex coverage — decision mapping paths', () => {
|
||||
|
||||
it('a context-only UserPromptSubmit hook DELEGATES so a later listener can still block', async () => {
|
||||
// Context alone is not a veto: a downstream agent/prompt-submit listener (a
|
||||
// policy plugin registered after the bridge) must still get to block. Before
|
||||
// the fix the bridge returned `allow` without calling next().
|
||||
// policy plugin registered after the bridge) must still get to block. The
|
||||
// bridge delegates via next() and folds its context onto the decision.
|
||||
const d = dir()
|
||||
hooks(d, { UserPromptSubmit: [{ hooks: [{ type: 'command', command: sh(d, 'c.sh', '#!/usr/bin/env bash\necho \'{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"bridge ctx"}}\'\n') }] }] })
|
||||
const adapter = new MockAdapter([textResponse('should not run')])
|
||||
|
||||
Reference in New Issue
Block a user