docs: audit hook compatibility limits
This commit is contained in:
@@ -1,17 +1,16 @@
|
||||
/**
|
||||
* Parse a Codex `hooks.json` into the shared {@link MatcherGroup} shape. Codex's
|
||||
* config format is a SUBSET of Claude Code's: the same event-name → matcher-group
|
||||
* structure and the same `{ type: 'command', command, timeout?/timeoutSec? }`
|
||||
* hook shape, but only five events and NO command-string substitution (Codex sets
|
||||
* no hook env vars and does not expand `${…}`). Non-command hooks (and Codex's
|
||||
* `async: true` commands) are parsed-and-skipped with a warning.
|
||||
* Parse the bridge-supported subset of a Codex `hooks.json` into the shared
|
||||
* {@link MatcherGroup} shape. The bridge accepts five events and the
|
||||
* `{ type: 'command', command, timeout?/timeoutSec? }` hook shape, performs no
|
||||
* config-time placeholder substitution or plugin-env injection, and skips
|
||||
* non-command and `async: true` handlers with a warning.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-hooks-codex/config
|
||||
*/
|
||||
|
||||
import type { MatcherGroup } from '@deepseek-ai/dsh-hook-protocol'
|
||||
|
||||
/** The five hook points Codex's engine supports. */
|
||||
/** The five current Codex hook points this bridge supports. */
|
||||
export const CODEX_EVENTS = ['PreToolUse', 'PostToolUse', 'SessionStart', 'UserPromptSubmit', 'Stop'] as const
|
||||
|
||||
/** A parsed Codex config: event name → its matcher groups (command hooks only). */
|
||||
@@ -37,10 +36,10 @@ function asObject(value: unknown): Record<string, unknown> | undefined {
|
||||
|
||||
/**
|
||||
* Parse a raw Codex `hooks.json` object into runnable {@link MatcherGroup}s.
|
||||
* Only the five {@link CODEX_EVENTS} are honored; an unknown event is dropped.
|
||||
* Only the five bridge-supported {@link CODEX_EVENTS} are honored; another event is dropped.
|
||||
* `type !== 'command'` and `async: true` command hooks are skipped (recorded in
|
||||
* `skipped`). Malformed entries are ignored rather than thrown — a bad config
|
||||
* must not crash boot. No command substitution (Codex does none).
|
||||
* must not crash boot. No config-time placeholder substitution is performed.
|
||||
* @param raw - the parsed JSON config: a `{ hooks: … }` wrapper or the bare event map.
|
||||
* @returns the runnable per-event groups plus the skipped hooks with their reasons.
|
||||
*/
|
||||
|
||||
@@ -3,14 +3,13 @@
|
||||
* `hooks.json` on the harness's canonical interception seams. The CODEX DIALECT
|
||||
* half of the hooks subsystem.
|
||||
*
|
||||
* Codex's hook protocol is a deliberate SUBSET of Claude Code's: five hook points
|
||||
* (`PreToolUse`, `PostToolUse`, `SessionStart`, `UserPromptSubmit`, `Stop` — no
|
||||
* subagent/notification/compaction), regex-only matchers, snake_case stdin
|
||||
* payloads with `turn_id`/`model` extras and NO trailing newline, no env vars and
|
||||
* no command substitution, and a block-only decision model (allow/ask are not
|
||||
* honored — a hook can only block, never pre-approve). The dialect-agnostic
|
||||
* This bridge supports five of Codex's ten current hook points (`PreToolUse`,
|
||||
* `PostToolUse`, `SessionStart`, `UserPromptSubmit`, and `Stop`), regex-only
|
||||
* matchers, snake_case stdin payloads with `turn_id`/`model` extras and no
|
||||
* trailing newline, no config-time placeholder substitution or plugin-env
|
||||
* injection, and no pre-tool approval or rewrite path. The dialect-agnostic
|
||||
* primitives come from `@deepseek-ai/dsh-hook-protocol`; this bridge owns the
|
||||
* Codex-specific payloads + matcher mode + decision mapping.
|
||||
* Codex-shaped payloads, matcher mode, and decision mapping.
|
||||
*
|
||||
* @module @deepseek-ai/dsh-hooks-codex
|
||||
*/
|
||||
@@ -263,9 +262,9 @@ export function apply(ctx: Context, config: Config): void {
|
||||
})
|
||||
|
||||
// Stop → ContinuationDecision. A blocking Stop hook forces continuation.
|
||||
// TODO(stop-loop-guard): like CC, a Stop hook that unconditionally blocks would
|
||||
// force-continue every step (`stop_hook_active` is always false here); the
|
||||
// loop-guard (stop_hook_active + a max-consecutive cap) is deferred.
|
||||
// TODO(stop-loop-guard): Codex supplies `stop_hook_active` so a Stop hook can
|
||||
// avoid continuing the same turn indefinitely. It is always false here, so an
|
||||
// unconditionally blocking hook force-continues every step until it self-limits.
|
||||
ctx.on('agent/turn-continuation', async (agent, turn, _default, next): Promise<ContinuationDecision> => {
|
||||
const merged = await runPoint('Stop', '', { ...turnBase(agent, 'Stop', model), stop_hook_active: false, last_assistant_message: null }, { agent, turn })
|
||||
/* jscpd:ignore-end */
|
||||
|
||||
Reference in New Issue
Block a user