hook

package
v1.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 21, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

Documentation

Overview

Package hook runs user-configured shell-command hooks around the agent loop: PreToolUse / PostToolUse fire around each tool call, UserPromptSubmit before a turn, Stop after it. Hooks come from settings.json — a project (.ok/settings.json, only when the project is trusted) and a global (~/.ok/settings.json) file. A hook's exit code is its verdict: 0 = pass, 2 = block (only on the gating events), other = warn. The payload is delivered as JSON on stdin; output is captured (capped) and surfaced to the user. This package only loads, matches, and runs hooks; the agent and controller decide what a block means (see internal/agent, internal/control).

Index

Constants

View Source
const (
	SettingsDirname  = ".ok"
	SettingsFilename = "settings.json"
)

SettingsDirname / SettingsFilename locate a scope's settings.json.

View Source
const TrustFilename = "trust.json"

TrustFilename is the user-global trust store under ~/.ok.

Variables

Events is every event, in a stable order — drives loading and `/hooks`.

Functions

func FormatOutcome

func FormatOutcome(o Outcome) string

FormatOutcome renders a non-pass outcome as a one-line human message.

func GlobalSettingsPath

func GlobalSettingsPath(homeDir string) string

GlobalSettingsPath is ~/.ok/settings.json (homeDir overrides ~).

func IsBlocking

func IsBlocking(e Event) bool

IsBlocking reports whether a non-zero/exit-2 (or timed-out) hook on this event can block the loop. Only the gating events qualify.

func IsTrusted

func IsTrusted(projectRoot, homeDir string) bool

IsTrusted reports whether projectRoot has been trusted to run its hooks.

func MatchesTool

func MatchesTool(h ResolvedHook, toolName string) bool

MatchesTool reports whether a hook applies to toolName. The match field is an anchored regex; non-tool events always match. A malformed regex never fires (safer than firing on everything).

func ProjectDefinesHooks

func ProjectDefinesHooks(projectRoot string) bool

ProjectDefinesHooks reports whether a project's settings.json exists and declares at least one hook — regardless of trust. Frontends use this to decide whether to prompt the user to trust the project.

func ProjectSettingsPath

func ProjectSettingsPath(projectRoot string) string

ProjectSettingsPath is <root>/.ok/settings.json.

func Trust

func Trust(projectRoot, homeDir string) error

Trust marks projectRoot as trusted, persisting the flag. Idempotent.

func TrustPath

func TrustPath(homeDir string) string

TrustPath is ~/.ok/trust.json (homeDir overrides ~).

Types

type Decision

type Decision string

Decision is a single hook invocation's verdict.

const (
	DecisionPass  Decision = "pass"
	DecisionBlock Decision = "block"
	DecisionWarn  Decision = "warn"
	DecisionError Decision = "error" // spawn failed (ENOENT, EACCES, …)
)

type Event

type Event string

Event is a point in the agent loop a hook can fire at.

const (
	PreToolUse       Event = "PreToolUse"
	PostToolUse      Event = "PostToolUse"
	UserPromptSubmit Event = "UserPromptSubmit"
	Stop             Event = "Stop"
)

type HookConfig

type HookConfig struct {
	// Match is an anchored regex selecting tools (Pre/PostToolUse only); "" or
	// "*" = every tool. Anchored: "file" won't match "read_file" — use ".*file".
	Match string `json:"match,omitempty"`
	// Command is the shell command to run (spawned through the platform shell).
	Command string `json:"command"`
	// Description is an optional human label surfaced in `/hooks`.
	Description string `json:"description,omitempty"`
	// Timeout overrides the per-event default, in milliseconds.
	Timeout int `json:"timeout,omitempty"`
	// Cwd overrides the working directory (defaults to the payload's cwd).
	Cwd string `json:"cwd,omitempty"`
}

HookConfig is one hook as written in settings.json.

type LoadOptions

type LoadOptions struct {
	ProjectRoot string
	HomeDir     string
	Trusted     bool
}

LoadOptions configure Load. Project hooks load only when Trusted; global hooks always load.

type Outcome

type Outcome struct {
	Hook      ResolvedHook
	Decision  Decision
	ExitCode  int // -1 when unknown (killed / spawn error)
	Stdout    string
	Stderr    string
	TimedOut  bool
	Truncated bool
	Duration  time.Duration
}

Outcome records one hook invocation.

type Payload

type Payload struct {
	Event         Event           `json:"event"`
	Cwd           string          `json:"cwd"`
	ToolName      string          `json:"toolName,omitempty"`
	ToolArgs      json.RawMessage `json:"toolArgs,omitempty"`
	ToolResult    string          `json:"toolResult,omitempty"`
	Prompt        string          `json:"prompt,omitempty"`
	LastAssistant string          `json:"lastAssistantText,omitempty"`
	Turn          int             `json:"turn,omitempty"`
}

Payload is the JSON envelope written to a hook's stdin.

type Report

type Report struct {
	Event    Event
	Outcomes []Outcome
	Blocked  bool // at least one outcome blocked (only meaningful on gating events)
}

Report aggregates the outcomes of running an event's hooks.

func Run

func Run(ctx context.Context, payload Payload, hooks []ResolvedHook, spawner Spawner) Report

Run executes the hooks matching payload.Event (and, for tool events, the tool name), feeding each the JSON payload on stdin. It stops at the first block so a gating hook can prevent later hooks running against a phantom success.

type ResolvedHook

type ResolvedHook struct {
	HookConfig
	Event  Event
	Scope  Scope
	Source string // absolute path to the settings.json it came from
	// contains filtered or unexported fields
}

ResolvedHook is a loaded hook with its origin baked in.

func Load

func Load(opts LoadOptions) []ResolvedHook

Load resolves hooks: project first (only when trusted), then global; within a scope, settings.json array order. A malformed file yields no hooks (never an error — a typo shouldn't take down the CLI).

type Runner

type Runner struct {
	// contains filtered or unexported fields
}

Runner binds a set of resolved hooks to a session: a working directory, the spawner, and a notify callback that surfaces non-blocking hook messages to the user. It is the single object the agent (tool events) and the controller (prompt/stop events) fire hooks through, so neither has to know how hooks load or run. A nil *Runner is a valid no-op (no hooks configured).

func NewRunner

func NewRunner(hooks []ResolvedHook, cwd string, spawner Spawner, notify func(string)) *Runner

NewRunner builds a Runner. spawner nil uses DefaultSpawner; notify nil drops non-blocking messages.

func (*Runner) ConsumeRollback

func (r *Runner) ConsumeRollback() (string, string, bool)

ConsumeRollback reports whether the last tool was rolled back. Shell-hook Runners never roll back — only the DST compile/test hooks do.

func (*Runner) Enabled

func (r *Runner) Enabled() bool

Enabled reports whether any hooks are configured.

func (*Runner) Hooks

func (r *Runner) Hooks() []ResolvedHook

Hooks returns the resolved hooks (for `/hooks` listing).

func (*Runner) PostToolUse

func (r *Runner) PostToolUse(ctx context.Context, name string, args json.RawMessage, result string)

PostToolUse fires after a tool call. It can't block; non-pass outcomes are surfaced to the user via notify.

func (*Runner) PreToolUse

func (r *Runner) PreToolUse(ctx context.Context, name string, args json.RawMessage) (block bool, message string)

PreToolUse fires before a tool call. block=true means the call must be refused; message is the reason (fed back to the model and shown to the user).

func (*Runner) PromptSubmit

func (r *Runner) PromptSubmit(ctx context.Context, prompt string, turn int) (block bool, message string)

PromptSubmit fires before a turn starts. block=true aborts the turn; message is the reason.

func (*Runner) Stop

func (r *Runner) Stop(ctx context.Context, lastAssistant string, turn int)

Stop fires after a turn finishes. It can't block.

type Scope

type Scope string

Scope records which settings.json a hook came from. Project hooks fire before global ones.

const (
	ScopeProject Scope = "project"
	ScopeGlobal  Scope = "global"
)

type Settings

type Settings struct {
	Hooks map[Event][]HookConfig `json:"hooks"`
}

Settings is the shape of a settings.json (only hooks for now).

type SpawnInput

type SpawnInput struct {
	Command string
	Cwd     string
	Stdin   string
	Timeout time.Duration
}

SpawnInput / SpawnResult / Spawner are the test seam around the real spawn.

type SpawnResult

type SpawnResult struct {
	ExitCode  int
	Stdout    string
	Stderr    string
	TimedOut  bool
	SpawnErr  error
	Truncated bool
}

func DefaultSpawner

func DefaultSpawner(ctx context.Context, in SpawnInput) SpawnResult

DefaultSpawner runs the command through the platform shell with the payload on stdin, capping captured output and honoring both the per-hook timeout and the parent context's cancellation.

type Spawner

type Spawner func(ctx context.Context, in SpawnInput) SpawnResult

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL