返回 CodeWhale
types.ts
1 /**
2 * Dialect-neutral vocabulary and log-only events shared by the Claude Code and
3 * Codex hook bridges. Payload construction, matching differences, environment,
4 * and extension-point-specific decision mapping remain owned by each bridge.
5 * @module @deepseek-ai/dsh-hook-protocol/types
6 */
7
8 /**
9 * The bridge that ran a hook — the CC bridge stamps `'claude-code'`, the Codex
10 * bridge `'codex'`. A native plugin at the interception points is not a bridge
11 * and writes no `hook/*` invocation/result records (see the interception extension-points Agent Note).
12 */
13 export type HookDialect = 'claude-code' | 'codex'
14
15 /**
16 * One configured command hook (the `{ type: 'command', command, timeout? }`
17 * shape shared by both dialects). Non-command hook types (CC's `prompt`/`agent`/
18 * `http`) are parsed-and-skipped by a bridge, so only this shape reaches the
19 * runner.
20 */
21 export interface CommandHook {
22 /** The shell command line to run. */
23 command: string
24 /** Per-hook timeout in SECONDS (the wire unit); the runner converts to ms. */
25 timeoutSec?: number
26 }
27
28 /**
29 * One matcher group: a `matcher` pattern (absent / `''` / `'*'` = match-all)
30 * plus the command hooks that run when it matches. Both dialects share this
31 * shape (CC's `hooks.json` and Codex's `hooks.json`).
32 */
33 export interface MatcherGroup {
34 matcher?: string
35 hooks: CommandHook[]
36 }
37
38 /**
39 * How a matcher pattern is interpreted. Claude Code uses {@link literal} when the
40 * pattern is purely `[A-Za-z0-9_|]+` (pipe = exact-match alternation) and
41 * {@link regex} otherwise; Codex is always {@link regex}. The bridge picks the
42 * mode for its dialect.
43 */
44 export type MatcherMode = 'claude-code' | 'codex'
45
46 /**
47 * The dialect-neutral OUTCOME a hook produced, parsed from its exit code +
48 * stdout JSON + stderr by {@link parseHookOutput}. A bridge maps this onto a
49 * extension-point-specific typed Decision (PreToolDecision, PreStepDecision, …). Every field
50 * is OPTIONAL because a hook may exercise any subset; the bridge decides which
51 * fields are meaningful for its hook point and which it ignores (faithful-but-
52 * degraded — e.g. Codex ignores `allow`/`ask`).
53 */
54 export interface HookOutput {
55 /** The raw process exit code (`undefined` if the hook could not be run). */
56 exitCode: number | undefined
57 /** Trimmed stderr — the block-reason source on a blocking (exit 2) hook. */
58 stderr: string
59 /**
60 * Trimmed stdout, verbatim. On a clean exit a hook may emit PLAIN (non-JSON)
61 * stdout that the protocol renders as output (CC) or treats as
62 * `additionalContext` (Codex SessionStart/UserPromptSubmit) — so the bridge
63 * needs the raw text, not just the parsed structured fields. Empty string when
64 * the hook produced no stdout.
65 */
66 stdout: string
67 /**
68 * `false` ⇒ the hook asked to halt (CC/Codex `continue:false`); pairs with
69 * {@link stopReason}. `true`/absent ⇒ proceed.
70 */
71 continue?: boolean
72 /** Human-readable reason shown when {@link continue} is `false`. */
73 stopReason?: string
74 /**
75 * The neutral blocking decision a hook expressed, folded from the two channels
76 * the reference protocols keep DISTINCT: the legacy top-level `decision`
77 * (`approve`/`block` only) and `hookSpecificOutput.permissionDecision`
78 * (`allow`/`deny`/`ask`). We normalize them to one enum — `'block'`/`'deny'`
79 * forbid, `'approve'`/`'allow'` permit, `'ask'` requests confirmation — but
80 * `'allow'`/`'deny'`/`'ask'` arise ONLY from a `permissionDecision`, never from
81 * a top-level `decision` (an out-of-band `{"decision":"deny"}` is invalid and
82 * ignored, matching the schemas). Absent ⇒ no explicit decision (exit code governs).
83 */
84 decision?: 'approve' | 'allow' | 'block' | 'deny' | 'ask'
85 /** The reason/explanation accompanying {@link decision}. */
86 reason?: string
87 /**
88 * Event discriminator claimed by `hookSpecificOutput`. On mismatch,
89 * {@link parseHookOutput} preserves this value but discards event-scoped fields.
90 */
91 hookEventName?: string
92 /** Extra context to inject for the next model request (CC `additionalContext`). */
93 additionalContext?: string
94 /** A warning surfaced to the user (CC `systemMessage`). */
95 systemMessage?: string
96 /**
97 * A tool-input rewrite a hook requested (CC `updatedInput`). PARSED but NOT
98 * honored — input rewrite is deferred (see the interception extension-points Agent Note); a
99 * bridge logs + warns when this is present.
100 */
101 updatedInput?: Record<string, unknown>
102 }
103
103 lines TYPESCRIPT