An event is a name a hook can be registered for. When it fires your function is handed the payload below, along with the next it may pass control to, and what it returns has to be the result type on the same row or the runtime treats the hook as having said nothing.
The declarations keep these as two maps, one from event name to payload and one from event name to result. They are joined here by name, because somebody reading about tool.pre wants both halves at once. The map each group came from is on its heading, so a name can be traced back to the declaration it was mined from.
EngineEventOf
#
38 events
Results come from EngineResultOf.
tool.call #
- Payload
ToolCallInput- Result
ToolCallResult
Fires when the engine is about to run a tool. `next(e)` runs the hooks beneath, then core (the permission prompt, the tool itself).
Return `{ deny: reason }` to refuse or `{ result }` to answer yourself; a hook that returns while its `next` is pending aborts what runs beneath. The managed-settings hooks run first: their deny is the call's result.
tool.check #
- Payload
ToolCheckInput- Result
ToolCheckResult
Fires when the engine decides whether a tool call may run, after the `tool.call` and PreToolUse hooks and before the mode settles an ask.
`next(e)` resolves to the engine's verdict (rules, mode, the tool's own check, PreToolUse's decision); return any `{ decision }`. `$.tool.check` runs the same chain and executes nothing.
on("tool.check", { tool: "Read" }, () => ({ decision: "allow" }))
ui.render #
- Payload
RenderInput- Result
RenderElement
Fires when the engine is about to draw a component: once per input value (props, viewport width), plugin load or `$.ui.invalidate("ui.render")`.
A repaint reuses the answer; a clock invalidates. `next(e)` resolves to the drawing: return it, wrap it, draw your own, or rewrite `props`. A tree that does not validate draws the engine's own; `--plugin-dir` is told why.
ui.resolve #
- Payload
ResolveInput- Result
ElementTable
Fires when the plugins load (not per draw), once per surface, component and plugin: `e` names the surface and component, never the props.
`next(e)` resolves to the surface's table, which `$.ui.resolve(e)` then reads. Return it, one with an element restyled for every other plugin, or one with a key left out (a fragment there); own table: hook skipped.
ui.press #
- Payload
UiPressArgument- Result
UiPressResult
Fires when a `Button` a render hook drew is pressed on a surface; `e` is `{ plugin, element, component, surface }`, `element` the button's `key`.
`next(e)` runs the hooks beneath, then core: the element's own `onPress` closure, in its plugin's environment, resolving to `{ element }`. Return `next(e)` to let the press through, or `{ element }` to take it.
ui.input #
- Payload
UiInputArgument- Result
UiInputResult
Fires when an `Input` a render hook drew changes or is submitted; `e` is `{ plugin, element, component, surface, kind, value }`.
`next(e)` runs the hooks beneath, then the element's own `onInput` or `onSubmit` with `e.value` as the chain left it, resolving to `{ element, value }`; `next({ ...e, value })` rewrites the typing, an answer takes it.
ui.select #
- Payload
UiSelectArgument- Result
UiSelectResult
Fires when a `Select` a render hook drew is picked from; `e` is `{ plugin, element, component, surface, value }`.
`next(e)` runs the hooks beneath, then the element's own `onSelect` with `e.value` as the chain left it, resolving to `{ element, value }`; `next({ ...e, value })` rewrites the pick, an answer takes it.
ui.message #
- Payload
UiMessageArgument- Result
UiMessageResult
Fires when a `Client` THIS plugin drew posts from its surface module (`surface.post(data)`); only this plugin's hooks see it.
`next.origin` names `client`: `data` came from code. Core answers `{}`; `next({ ...e, data })` rewrites the data; `{ props }` hands the posting instance its next props without a redraw. One per instance per frame.
ui.scroll #
- Payload
UiScrollInput- Result
UiScrollResult
Fires before a site's window moves: the person's wheel or scroll keys on a `Pane` body or the `AbovePrompt` band, at its edges too; `$.ui.scroll`.
`next(e)` moves it to `e.offset` and draws: `{}`; `next({ ...e, offset })` elsewhere; no `next` (`{}` or `{ deny }`) leaves it undrawn, so a hook drawing its own rows under a header moves them by `e.by` and invalidates.
on("ui.scroll", { requestId: "log" }, ($, e) => (scrollOwnRows(e.by), {}))
ui.focus #
- Payload
UiFocusInput- Result
UiFocusResult
Fires before a site's focus ring moves: the person's Tab, arrows or click in a `Pane` or the band; an `autoFocus` element taking it; `$.ui.focus`.
`next(e)` lands it on `e.element` (absent: one of the engine's stops) and draws: `{}`; `next({ ...e, element })` on another of `e.plugin`'s; no `next` (`{}` or `{ deny }`) keeps it where it was, drawn as it was.
on("ui.focus", { requestId: "list" }, ($, e, next) => (mark(e), next(e)))
agent.offer #
- Payload
AgentOfferInput- Result
AgentOfferResult
Fires when the engine offers an agent type to the model, in the agent listing and again at dispatch; `next(e)` resolves to `{ isOffered: true }`.
Return `{ isOffered: false }` to keep the type out of the listing and refuse its dispatch. A hook that fails passes it through.
on("agent.offer", { agent: "Plan" }, () => ({ isOffered: false }))
agent.spawn #
- Payload
AgentSpawnInput- Result
AgentSpawnResult
Fires when the Agent tool is about to start a subagent, everything decided and its model not yet resolved.
`next(e)` resolves to `{ model }`. Return it, `next({ ...e, model })`, `{ model }` of your own (an alias resolves like the tool's parameter), or `{ deny: reason }`.
prompt.submit #
- Payload
PromptSubmitInput- Result
PromptSubmitResult
Fires when a prompt is submitted, before the turn starts. `next(e)` runs the hooks beneath and the UserPromptSubmit settings hooks.
Rewrite with `next({ ...e, text })` (the user message on screen follows) or stop it with `{ drop: reason }`; a broken plugin never blocks a prompt. A prompt typed while a turn ran fires at Enter, with that turn's id.
prompt.fill #
- Payload
PromptFillInput- Result
PromptFillResult
Fires when a text is about to be put in the prompt box as the person's draft (a plugin's `$.prompt.fill`); `next(e)` writes it by `e.mode`.
`replace` over the draft, `append` after it, `insert` at the cursor; rewrite `text` or `mode` going down, or answer `{ isFilled: false }` without `next` to keep it out, as core does under a dialog or headless.
on("prompt.fill", ($, e, next) => next({ ...e, text: e.text.trim() }))
prompt.suggest #
- Payload
PromptSuggestInput- Result
PromptSuggestResult
Fires when a text is proposed as the prompt box's dim suggestion, Tab to take: the engine's guess after a turn, or a plugin's `$.prompt.suggest`.
`next(e)` shows it: `{ isShown }`. Rewrite with `next({ ...e, text })`, or answer `{ isShown: false }` without `next` to drop it; core answers that too while the box holds text or a turn runs.
on("prompt.suggest", { origin: { kind: "suggestion" } }, hide)
prompt.edit #
- Payload
PromptEditInput- Result
PromptEditResult
Fires when the person edits the main prompt box: a key the editor took as an edit, or a paste; `next(e)` resolves the box the editor shows.
`e` is the draft before and the splice (`start`, `end`, `inputText`); a burst of keys is one edit. Rewrite `inputText` going down or the box coming up; `{ text: e.text, cursor: e.cursor }` without `next` consumes.
on("prompt.edit", ($, e, next) => next({ ...e, inputText: up(e) }))
prompt.section #
- Payload
PromptSectionInput- Result
PromptSectionResult
Fires once per named section of the system prompt, when the engine assembles it; `next(e)` resolves to `{ text }` as core computed it.
Sections are cached by name for the session until `$.ui.invalidate("prompt.section")`: an unstable answer spends the model's prompt cache on every call. A hook that fails passes it through.
on("prompt.section", { name: "memory" }, () => ({ text: null }))
prompt.context #
- Payload
PromptContextInput- Result
PromptContextResult
Fires once per conversation, when the engine computes the context blocks its first user message carries; `next(e)` resolves to `{ blocks }`.
Append, drop, reorder or rewrite with `next({ ...e, blocks })`; the engine renders what comes back, in order, until `$.ui.invalidate("prompt.context")` or a re-read (compaction, `/clear`).
on("prompt.context", () => ({ blocks: [] }))
prompt.attachment #
- Payload
PromptAttachmentInput- Result
PromptAttachmentResult
Fires once per message the engine injects for the model on its own (a reminder, a mode transition, a mentioned file), as a request carries it.
`next(e)` resolves to `{ text }`; `{ text: null }` leaves it out. The answer holds per attachment for the process (asked again on resume or `$.ui.invalidate`); the transcript keeps the engine's record.
on("prompt.attachment", { type: "todo_reminder" }, () => ({ text: null }))
tool.describe #
- Payload
ToolDescribeInput- Result
ToolDescribeResult
Fires once per tool, when the engine first renders the tool's schema in a session; `next(e)` resolves to `{ description, isDeferred? }`.
Cached for the session until `$.ui.invalidate("tool.describe")`: an unstable answer spends the model's prompt cache. An explicit `isDeferred` moves the tool behind ToolSearch (true) or into the prompt's list (false).
on("tool.describe", { tool: "Bash" }, ($, e) => ({ ...e, description }))
on("tool.describe", { tool: "Monitor" }, pin) // {...e, isDeferred: false}
command.run #
- Payload
CommandRunInput- Result
CommandRunResult
Fires when a slash command is about to run (`/name args` typed, or a plugin's `$.command.run`); `next(e)` resolves to `{ text }`, its output.
Core is the engine's command (a registered one has none). Rewrite `args` with `next`, or return `{ text }` without it to answer in its place; one after `next` replaces a printed output, not a panel or prompt it opened.
on("command.run", { command: "hello" }, () => ({ text: "hello" }))
command.describe #
- Payload
CommandDescribeInput- Result
CommandDescribeResult
Fires once per command, when the engine lists it for the typeahead and `/help`; `next(e)` resolves to `{ description, argumentHint, isHidden }`.
Listed answers are cached for the session until `$.ui.invalidate("command.describe")`. A hook that fails passes it through.
on("command.describe", ($, e, next) => next({ ...e, isHidden: true }))
config.set #
- Payload
ConfigSetInput- Result
ConfigSetResult
Fires when a `/config` row is about to change, from the menu or a plugin's `$.config.set`; `next(e)` resolves to `{ value }` once written.
Return `{ deny: reason }` to leave the row as it is (the menu says why), or `next({ ...e, value })` to clamp it; a value of the wrong kind for the row is refused. A row a trusted source owns is core's to refuse.
on("config.set", { key: "theme" }, () => ({ deny: "the theme stays" }))
config.describe #
- Payload
ConfigDescribeInput- Result
ConfigDescribeResult
Fires once per `/config` row, when the menu lists it and for `$.config.list`; `next(e)` resolves to `{ label, description, isHidden }`.
Relabel, re-describe or hide with `next({ ...e, isHidden: true })`; the answers are cached until `$.ui.invalidate("config.describe")` or the loaded plugins change. A hook that fails passes the row through.
on("config.describe", { key: "tips" }, hide) // answers isHidden: true
skill.prompt #
- Payload
SkillPromptInput- Result
SkillPromptResult
Fires when the engine expands a skill's prompt for the model (`/name`, the Skill tool, a preload); `next(e)` resolves to `{ text }` as computed.
Return `{ text }` with the text the model reads instead. A hook that fails passes it through.
on("skill.prompt", { skill: "commit" }, () => ({ text: "A haiku." }))
attribution.text #
- Payload
AttributionTextInput- Result
AttributionTextResult
Fires when the engine composes a git text the model is to write (`kind`: `commit`, `pr`, `exemption`, `remedy`); `next(e)` resolves to `{ text }`.
Return `{ text }` with the text the model reads instead. A hook that fails passes it through.
on("attribution.text", { kind: "commit" }, () => ({ text: "" }))
session.start #
- Payload
SessionStartInput- Result
SessionStartResult
Fires once per process for each loaded plugin, before the first prompt, then once per fresh load of one (never `/clear`); `next(e)` is `{ cwd }`.
Observe. The first is awaited: a `$.tool.register` is listed by turn one. A later one runs its hooks alone: an enable, a worker respawn, or a reload (changed modules only; all if one hooks `engine.create`/`plugin.register`).
on("session.start", ($, e, next) => $.tool.register(t).then(() => next(e)))
session.receive #
- Payload
SessionReceiveInput- Result
SessionReceiveResult
Fires when a delivery reaches the session (a relay's event, a peer's message, a Remote Control prompt), before it is queued; `{ text }`.
Rewrite with `next({ ...e, text })`, or return `{ consumed: reason }` to take it: nothing is queued, shown or read by the model. `origin` and `event` pass on as received; `session.send`, its dual, is reserved.
on("session.receive", { origin: "peer" }, () => ({ consumed: "muted" }))
session.compact #
- Payload
SessionCompactInput- Result
SessionCompactResult
Fires when the conversation is about to be compacted (`/compact`, the threshold, a plugin, or ahead of time); `next(e)` resolves `{ messages }`.
Rewrite `instructions` or `messages` on the way down, the messages on the way up, or answer `{ messages }` of your own; `{ skip: reason }` leaves the conversation as it is. `trigger` passes on as received.
on("session.compact", { trigger: "precompute" }, () => ({ skip: "off" }))
session.attach #
- Payload
SessionAttachInput- Result
SessionAttachResult
Fires when a remote client joins the session's roster of attached surfaces: it said so (ui_attach), or it first asked to draw.
Observe (a phone joined: draw the lobby); `next(e)` resolves to `{ clientId }`, a different return changes nothing. `$.session.surfaces()` reads the roster; a render hook still reads `e.surface` per ask.
on("session.attach", { surface: "mobile" }, ($, e, next) => next(e))
session.detach #
- Payload
SessionDetachInput- Result
SessionDetachResult
Fires when a client leaves the roster: it detached, or the session ended with it attached (`e.reason`). Observe; `next(e)` echoes `{ clientId }`.
session.measure #
- Payload
SessionMeasureInput- Result
SessionMeasureResult
Fires when the engine measures the session and a unit moved: after each main-thread turn, and when a rate-limit window moves a whole point.
Observe; `next(e)` echoes `{ changed }`. `$.session.usage()`'s figures, pushed, not polled: compare them with your own threshold here, call the op for the breakdown. One at a time, a burst folding into one more.
on("session.measure", ($, e, next) => (toastPast90(e.rateLimits), next(e)))
session.end #
- Payload
SessionEndInput- Result
SessionEndResult
Fires once when the session ends (exit, /clear, resume, logout, signal, a `-p` run done), after its SessionEnd settings hooks; `e.reason` says which.
`e.resume.id` is `--resume`'s id; `next(e)` runs the engine's end step for the plugins (at an exit the attached clients leave): `{ sessionId }`. The SessionEnd bound afresh, 1.5 s by default; a `kill -9` raises nothing.
on("session.end", async ($, e, next) => (await keep(e.resume), next(e)))
plugin.register #
- Payload
PluginRegisterInput- Result
PluginRegisterResult
Fires once per hooks module about to join the chain, at load (the set folded and built, nothing swapped in) and at reload; core allows.
Return `{ refuse: reason }` and it never joins: no hook, no noun, no tool of it; the transcript names who refused. Its judges, `$` whole, are the plugins admitted before it and the binary's; judge by `tier` and `uses`.
on("plugin.register", { tier: "user" }, () => ({ refuse: "managed only" }))
turn.start #
- Payload
TurnStartInput- Result
TurnStartResult
Fires when a model turn begins, before its first model call; `next(e)` resolves to `{ turnId }`. Observe: a different return changes nothing.
turn.step #
- Payload
TurnStepInput- Result
TurnStepResult
Fires when the engine is about to send a model request of a turn, main's or a subagent's (`e.agentId`); `next(e)` resolves to the whole response.
`next({ ...e, model })` or `effort` sends another; the turn, the index and the message count are pinned. An answer without `next` sends no request. It streams (StreamNext): the hook's budget counts its own code alone.
turn.complete #
- Payload
TurnCompleteInput- Result
TurnCompleteResult
Fires when a model turn has ended, at the point its duration is reported; `next(e)` resolves to `{ text }`, the answer. `e.reason` says why.
Return `{ text }` with a different text to show it beneath the answer (a synopsis, a TL;DR line); the transcript's record is never rewritten. A hook that fails leaves the answer as it was.
engine.create #
- Payload
EngineCreateInput- Result
EngineCreateResult
Runs while `$` is being built, once per load or reload of this plugin and before any other hook of it; `next(e)` resolves to `$` built so far.
A step may ADD nouns and WITHHOLD nouns (leave one out, or return without `next`); it may NOT REPLACE one another step added: the step fails, named with both plugins. A step that fails unloads its plugin; `$` is rebuilt.
OpEventOf
#
33 events
No result map is declared for these, so a hook here returns nothing the runtime reads.
agent.register #
- Payload
AgentSpec
The argument of `$.agent.register(spec)`: the agent type as the plugin defined it. A hook above rewrites any of it; the type stays the caller's.
ui.open #
- Payload
PaneOpenArgs
The argument of `$.ui.open({ id, title, focus })`; a hook above the opener may retitle it or refuse it with `{ deny }`, never rename it.
ui.close #
- Payload
PaneCloseInput
The argument of `$.ui.close({ id })` with `origin` `plugin`; the engine raises it too, for the person (`person`) and an unload (`unload`).
ui.blit #
- Payload
UiBlitArgs
The argument of `$.ui.blit(...)`: a Raster's `cells` or a keyed Image's `source`; a hook above may rewrite either with `next`, or `{ deny }`.
clock.sleep #
- Payload
ClockWait
The argument of `$.clock.sleep(ms, { signal })`; the signal does not cross, it aborts the dispatch.
clock.after #
- Payload
ClockWait
The argument of `$.clock.after(ms, fn)`: the wait before `fn`, which stays in the plugin's environment and runs once the dispatch resolves.
clock.every #
- Payload
ClockWait
The argument of `$.clock.every(ms, fn)`, dispatched once per period: `fn` runs each time a dispatch resolves, and the next period is asked.