Documentation
¶
Overview ¶
Package hooks implements jcode's user-configurable hook system: external commands fired at key points of the agent loop (before/after a tool runs, on session start, when the agent is about to stop, etc.).
The package is a dependency-free leaf: internal/agent, internal/runner and the command surfaces depend on it, never the other way around. This keeps the hook dispatcher transport-agnostic so TUI, Web and ACP all share one implementation.
The on-disk schema and the stdin/stdout protocol mirror Claude Code / Qoder so users can reuse familiar configs. See internal-doc/hooks-design.md.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsPreApproved ¶
IsPreApproved reports whether a PreToolUse hook already authorized this call.
func WithDispatcher ¶
func WithDispatcher(ctx context.Context, d Dispatcher) context.Context
WithDispatcher stores the session's Dispatcher on the context so the tool hook middleware and the runner's continuation loop can reach it without threading it through every signature. Command surfaces (TUI/Web/ACP) inject it into the ctx they hand to the agent run.
func WithPreApproved ¶
WithPreApproved marks the context so a downstream approval gate skips its user prompt. The PreToolUse hook middleware sets this when a hook returns permissionDecision=allow; the approval layer reads it via IsPreApproved.
It is intentionally per-call (context.WithValue, not persisted across interrupt/resume): a pre-approval only applies to the single tool invocation whose ctx carries it.
Types ¶
type Config ¶
Config is one parsed hooks.json file: event name → matcher groups.
func Load ¶
Load reads and merges the hooks.json layers.
configDir — the ~/.jcode directory (config.ConfigDir()).
workDir — the project working directory; its .jcode/ holds project layers.
trustProject — whether to load the project layers (.jcode/hooks.json and
.jcode/hooks.local.json).
SECURITY: only the user layer (~/.jcode/hooks.json) is trusted by default. A hook runs arbitrary commands the moment its event fires (SessionStart fires on startup) and can auto-approve tools, so honoring project-provided hooks would be arbitrary code execution from an untrusted clone. Project layers therefore load only when trustProject is true — a stand-in until trust-on-first-use (per-hook hash confirmation, see internal-doc/hooks-design.md §7) lands.
Missing files are skipped silently. Malformed files are skipped with a warning (returned, not fatal) so one broken layer cannot brick the agent.
type Decision ¶
type Decision struct {
// Permission is the folded PreToolUse verdict. PermDeny wins over everything.
Permission Permission
// Block is true when a blockable event decided to halt (exit 2 / decision=block).
Block bool
// Reason explains a deny/block, surfaced to the agent or user.
Reason string
// UpdatedInput, if non-nil, replaces tool_input before execution (PreToolUse).
UpdatedInput json.RawMessage
// ModifiedResult, if non-nil, replaces tool_response after execution (PostToolUse).
ModifiedResult *string
// AdditionalContext is text to inject into the model context.
AdditionalContext string
// SystemMessage is a non-blocking note surfaced to the user.
SystemMessage string
}
Decision is the folded outcome of firing all of an event's matching hooks. The zero value means "no hook had any opinion — proceed normally".
type Dispatcher ¶
type Dispatcher interface {
// Fire runs every matching hook for event with the given payload and returns
// the folded decision. The zero Decision means "no opinion — proceed".
Fire(ctx context.Context, event Event, p Payload) Decision
// Configured reports whether any hook is registered for event, letting hot
// paths skip payload construction when hooks are unused.
Configured(event Event) bool
}
Dispatcher fires the hooks configured for an event and folds their outcomes into a single Decision. Implementations must be safe for concurrent use.
func DispatcherFromContext ¶
func DispatcherFromContext(ctx context.Context) Dispatcher
DispatcherFromContext returns the Dispatcher on the context, or a no-op dispatcher when none was injected, so callers never need a nil check.
func NewDispatcher ¶
func NewDispatcher(cfg Config, opts Options) Dispatcher
NewDispatcher builds a Dispatcher from a merged Config. When the config defines no hooks it returns a no-op dispatcher whose Fire is free, so callers can wire it unconditionally.
func NewSessionDispatcher ¶
func NewSessionDispatcher(configDir, cwd, sessionID string, logf func(string, ...any)) Dispatcher
NewSessionDispatcher loads the hook config for a session and builds a Dispatcher, honoring the project-trust env gate. Shared by all command surfaces (TUI/Web/ACP) so hooks behave identically everywhere.
configDir — the ~/.jcode directory (config.ConfigDir()). cwd — the session working directory. sessionID — the session UUID (for the hook payload).
type HookSpec ¶
type HookSpec struct {
Type string `json:"type"` // v1 supports "command"
Command string `json:"command"` // shell command (run via `sh -c`)
Timeout int `json:"timeout,omitempty"` // seconds; 0 → dispatcher default
Async bool `json:"async,omitempty"` // fire-and-forget (non-blockable events only)
}
HookSpec is a single configured hook command.
type Options ¶
type Options struct {
CWD string
SessionID string
TranscriptPath string
Env []string // base env; nil → os.Environ()
Logf func(string, ...any)
}
Options parameterize a Dispatcher with per-session context and hooks.
type Payload ¶
type Payload struct {
SessionID string `json:"session_id,omitempty"`
TranscriptPath string `json:"transcript_path,omitempty"`
CWD string `json:"cwd,omitempty"`
HookEventName string `json:"hook_event_name"`
ToolName string `json:"tool_name,omitempty"`
ToolInput json.RawMessage `json:"tool_input,omitempty"`
ToolResponse string `json:"tool_response,omitempty"`
Prompt string `json:"prompt,omitempty"`
StopHookActive bool `json:"stop_hook_active,omitempty"`
}
Payload is the JSON handed to a hook process over stdin. Field names match the Claude Code contract so existing hook scripts work unchanged.
type Permission ¶
type Permission string
Permission is a PreToolUse decision.
const ( PermNone Permission = "" // hook expressed no opinion PermAllow Permission = "allow" PermDeny Permission = "deny" PermAsk Permission = "ask" )