Chapter 4: types, one atom of state
A hook call is over in milliseconds, and everything it knew goes with it.
But the band above the prompt has to show the last decision, long after
the hook that made it returned. So the mod keeps one piece of state: an
atom, Claude Code's name for a single named value a plugin can read and
update from any hook. jevhooks has exactly one, jevhooks.last, and this
folder says what shape it is.
export type Decision = { verdict: 'allow' | 'ask' | 'pass' | 'hold'; line: string; ms: number; session_usd: number }
export type ModelChoice = { tier?: 'haiku' | 'sonnet' | 'opus' | 'fable' | null; model?: string | null; line: string; ms: number; session_usd: number }
export type Shown = { verdict: Decision['verdict'] | 'model'; line: string; ms: number; session_usd: number } & {
kind: 'command' | 'turn end' | 'prompt'
}
declare module 'claude-code' {
interface PluginState {
jevhooks: { last: Shown | null }
}
}
Decision is what the daemon sends back for a deciding event, and
ModelChoice what it sends back for a prompt. Shown is either of them
as the band prints it, with which kind of thing was decided as its first
word.
The declare module block is TypeScript's way of adding a field to an
interface someone else owns: Claude Code's PluginState gains a
jevhooks entry, so read($, last) and update($, last, ...) in
register.tsx are checked against it.
Aside: the same type, written twice.
DecisionandModelChoicehere mirror the Rust structs of the same names incrates/jevhooks-events(chapter 8), field for field, and those copies are kept by hand. Rust and TypeScript do share one definition of what to do with an event, because the routing is Rust compiled to JavaScript. The daemon's reply crosses as JSON, and nothing generates this file from the Rust. If you add a field there, add it here.
Try it.
claude plugin validate pluginreportsdeclares state: jevhooks.last, and forregister.tsx,state writes: jevhooks.lastandstate reads: jevhooks.last: one atom, written bydecideandchooseModel, read by the band. With the engine's types in place,tsc -p pluginchecks every use.
For the people who maintain it
In this folder
| Path | What |
|---|---|
| index.d.ts | Decision, ModelChoice, Shown, and the plugin's entry in PluginState. The manifest points here ("types"). |
← Previous: Chapter 3, hooks/ · Up: plugin · Next: Chapter 5, tests/ →