Documentation
¶
Index ¶
Constants ¶
const BlockingExitCode = 2
BlockingExitCode is the exit status a command hook can use to deny an action without producing a structured JSON response. Mirrors Claude Code.
const HTTPBlockingStatus = 422
HTTPBlockingStatus is the HTTP status code an endpoint can use to deny an action without producing a structured JSON response. Picked to match "Unprocessable Entity" semantics; mirrors the spirit of CommandDriver's exit-2 contract.
Variables ¶
This section is empty.
Functions ¶
func NewDriverRegistry ¶
NewDriverRegistry builds the per-type driver map used by the HookManager. Drivers with external dependencies are included only when their caller is available; dispatch then skips the corresponding configured entries.
Types ¶
type CommandDriver ¶
type CommandDriver struct {
// contains filtered or unexported fields
}
CommandDriver runs a shell command for each invocation. The HookInput is serialized as JSON and written to the command's stdin. Communication follows the Claude-Code-compatible contract:
- Exit 0: success. Optional JSON on stdout may select the decision via {"decision":"block","reason":"..."}. Empty or non-JSON stdout = allow.
- Exit 2: explicit block; stderr text is surfaced as the reason.
- Other non-zero exit / timeout: fails open (allows) with a warning log.
When the sandbox is active, entry.Command runs through it exactly as the Bash tool's own commands do -- same wrap, same excluded_commands, same scrubbed environment -- so a hook cannot reach what the sandbox exists to contain. Unlike Bash, a hook has no dangerously_disable_sandbox escape hatch: hooks are config-authored automation, not an LLM-chosen call the operator is watching turn by turn, so there is no per-invocation argument for one to opt out with.
func NewCommandDriver ¶
func NewCommandDriver(sandbox agent.SandboxView) *CommandDriver
NewCommandDriver constructs a command driver. sandbox nil is treated as agent.NoopSandbox{} -- no enforcement, matching every other SandboxView consumer's nil-guard.
type Deps ¶
type Deps struct {
MCPCaller MCPCaller
LLMCaller LLMCaller
// Sandbox gates the command and http drivers the same way it gates the
// Bash and WebFetch tools, so a hook cannot reach a boundary those tools
// are confined by. Nil (the zero value) is treated as agent.NoopSandbox{}
// by NewCommandDriver/NewHTTPDriver, matching WithSandbox's nil-guard
// elsewhere -- no enforcement, today's pre-sandbox behavior.
Sandbox agent.SandboxView
}
Deps groups the externals that drivers need to be constructed. It is passed to NewDriverRegistry. Nil fields disable the corresponding driver type (the registry skips it instead of returning a broken implementation).
type Driver ¶
type Driver interface {
// Type returns the value the manager uses to address this driver. Must
// match the values produced by corehook.Entry.ResolvedType.
Type() string
// Run executes entry against in and returns the resulting decision.
Run(ctx context.Context, entry corehook.Entry, in agent.HookInput) agent.HookOutput
}
Driver executes one configured hook entry and returns the resulting HookOutput. Implementations are stateless except for cheap caches (e.g. compiled regex). Dispatch concerns — matcher evaluation, entry ordering, first-block-wins aggregation — live in the manager, not here.
A Driver must fail open on internal errors: hook failures should never silently break the agent loop. Returning a blocking HookOutput is reserved for explicit policy decisions surfaced by the hook itself (e.g. command exit 2, HTTP 422, JSON {"decision":"block"}).
type HTTPDriver ¶
type HTTPDriver struct {
// Client is the http.Client used for outbound requests. Tests inject
// their own; production constructs one with the per-entry timeout.
Client *http.Client
// contains filtered or unexported fields
}
HTTPDriver posts the HookInput JSON to entry.URL and parses the response.
- 2xx with no body / non-JSON body: allow.
- 2xx with JSON {"decision":"block","reason":"..."}: block.
- HTTPBlockingStatus (422): block with the response body as reason.
- Any other non-2xx, network error, or timeout: fail open with a warn.
Header values may contain "$VAR" / "${VAR}" placeholders. Only env vars listed in entry.AllowedEnv are substituted — this prevents accidental leakage of arbitrary process env into outbound requests.
When the sandbox is active, entry.URL's host is checked against the same allowed_domains/denied_domains policy WebFetch enforces, before the request is ever built — a hook is a non-bash egress path exactly like WebFetch is, so it honors the same allow-list rather than reaching the network unconstrained.
func NewHTTPDriver ¶
func NewHTTPDriver(sandbox agent.SandboxView) *HTTPDriver
NewHTTPDriver returns a driver with a default client. Per-entry timeout is applied via context, so the client itself does not need a global timeout. sandbox nil is treated as agent.NoopSandbox{}, matching every other SandboxView consumer's nil-guard.
type LLMCaller ¶
type LLMCaller interface {
CompleteHookPrompt(ctx context.Context, model, prompt string) (string, error)
}
LLMCaller runs a single-turn prompt against a model and returns the text content. An empty model means "use the default fast model". Implementations live in agentapp.
type MCPCaller ¶
type MCPCaller interface {
CallMCPTool(ctx context.Context, server, tool string, input map[string]any) (string, error)
}
MCPCaller invokes a named tool on a named MCP server. Implementations live in agentapp where MCPManager is wired; the MCPDriver depends only on this interface so infra/hook stays free of agentapp imports.
type MCPDriver ¶
type MCPDriver struct {
// contains filtered or unexported fields
}
MCPDriver invokes a tool on an already-connected MCP server as a hook. The driver delegates the actual MCP call to a MCPCaller (provided by agentapp). Values inside entry.Input may reference fields of the HookInput payload via "${field}" placeholders (e.g. "${tool_args.path}") so the hook script can pass per-event data to the MCP tool.
The tool's text result is parsed as JSON when it looks like a hook output object ({decision, reason}); other text is treated as allow.
func NewMCPDriver ¶
NewMCPDriver returns a driver backed by caller. A nil caller makes the driver fail open with a warning at dispatch time (consistent with other missing-driver scenarios).
type PromptDriver ¶
type PromptDriver struct {
// contains filtered or unexported fields
}
PromptDriver runs a single-turn LLM prompt as a hook. The literal "$ARGUMENTS" inside entry.Prompt is replaced with the HookInput JSON so the model can inspect every field of the event payload. The response is parsed as JSON looking for {decision, reason}; anything else is treated as allow.
Note: PromptDriver makes one LLM call per matching event, which can dominate turn latency if used on hot paths (PreToolUse / PostToolUse). Pair it with a cheap default model in settings.yaml.
func NewPromptDriver ¶
func NewPromptDriver(caller LLMCaller) *PromptDriver
NewPromptDriver returns a driver backed by caller.