diff --git a/docs/.i18n/glossary.zh-CN.json b/docs/.i18n/glossary.zh-CN.json index 99a8398890a..f744566cf92 100644 --- a/docs/.i18n/glossary.zh-CN.json +++ b/docs/.i18n/glossary.zh-CN.json @@ -343,6 +343,10 @@ "source": "Plugin Entry Points", "target": "插件入口点" }, + { + "source": "Plugin hooks", + "target": "插件钩子" + }, { "source": "Entry Points", "target": "入口点" diff --git a/docs/automation/hooks.md b/docs/automation/hooks.md index eefec6b7a40..0c3eb7465d5 100644 --- a/docs/automation/hooks.md +++ b/docs/automation/hooks.md @@ -205,9 +205,12 @@ Runs `BOOT.md` from the active workspace when the gateway starts. ## Plugin hooks -Plugins can register hooks through the Plugin SDK for deeper integration: intercepting tool calls, modifying prompts, controlling message flow, and more. The Plugin SDK exposes 28 hooks covering model resolution, agent lifecycle, message flow, tool execution, subagent coordination, and gateway lifecycle. +Plugins can register typed hooks through the Plugin SDK for deeper integration: +intercepting tool calls, modifying prompts, controlling message flow, and more. +Use plugin hooks when you need `before_tool_call`, `before_agent_reply`, +`before_install`, or other in-process lifecycle hooks. -For the complete plugin hook reference including `before_tool_call`, `before_agent_reply`, `before_install`, and all other plugin hooks, see [Plugin Architecture](/plugins/architecture-internals#provider-runtime-hooks). +For the complete plugin hook reference, see [Plugin hooks](/plugins/hooks). ## Configuration @@ -315,5 +318,5 @@ Check for missing binaries (PATH), environment variables, config values, or OS c - [CLI Reference: hooks](/cli/hooks) - [Webhooks](/automation/cron-jobs#webhooks) -- [Plugin Architecture](/plugins/architecture-internals#provider-runtime-hooks) — full plugin hook reference +- [Plugin hooks](/plugins/hooks) — in-process plugin lifecycle hooks - [Configuration](/gateway/configuration-reference#hooks) diff --git a/docs/automation/index.md b/docs/automation/index.md index bc3ac89cc11..bd0fed50685 100644 --- a/docs/automation/index.md +++ b/docs/automation/index.md @@ -40,7 +40,7 @@ flowchart TD | Audit what ran and when | Background Tasks | `openclaw tasks list` and `openclaw tasks audit` | | Multi-step research then summarize | Task Flow | Durable orchestration with revision tracking | | Run a script on session reset | Hooks | Event-driven, fires on lifecycle events | -| Execute code on every tool call | Hooks | Hooks can filter by event type | +| Execute code on every tool call | Plugin hooks | In-process hooks can intercept tool calls | | Always check compliance before replying | Standing Orders | Injected into every session automatically | ### Scheduled Tasks (Cron) vs Heartbeat @@ -83,7 +83,11 @@ See [Standing Orders](/automation/standing-orders). ### Hooks -Hooks are event-driven scripts triggered by agent lifecycle events (`/new`, `/reset`, `/stop`), session compaction, gateway startup, message flow, and tool calls. Hooks are automatically discovered from directories and can be managed with `openclaw hooks`. +Internal hooks are event-driven scripts triggered by agent lifecycle events +(`/new`, `/reset`, `/stop`), session compaction, gateway startup, and message +flow. They are automatically discovered from directories and can be managed +with `openclaw hooks`. For in-process tool-call interception, use +[Plugin hooks](/plugins/hooks). See [Hooks](/automation/hooks). @@ -97,7 +101,7 @@ See [Heartbeat](/gateway/heartbeat). - **Cron** handles precise schedules (daily reports, weekly reviews) and one-shot reminders. All cron executions create task records. - **Heartbeat** handles routine monitoring (inbox, calendar, notifications) in one batched turn every 30 minutes. -- **Hooks** react to specific events (tool calls, session resets, compaction) with custom scripts. +- **Hooks** react to specific events (session resets, compaction, message flow) with custom scripts. Plugin hooks cover tool calls. - **Standing orders** give the agent persistent context and authority boundaries. - **Task Flow** coordinates multi-step flows above individual tasks. - **Tasks** automatically track all detached work so you can inspect and audit it. @@ -108,6 +112,7 @@ See [Heartbeat](/gateway/heartbeat). - [Background Tasks](/automation/tasks) — task ledger for all detached work - [Task Flow](/automation/taskflow) — durable multi-step flow orchestration - [Hooks](/automation/hooks) — event-driven lifecycle scripts +- [Plugin hooks](/plugins/hooks) — in-process tool, prompt, message, and lifecycle hooks - [Standing Orders](/automation/standing-orders) — persistent agent instructions - [Heartbeat](/gateway/heartbeat) — periodic main-session turns - [Configuration Reference](/gateway/configuration-reference) — all config keys diff --git a/docs/cli/hooks.md b/docs/cli/hooks.md index 8ddb5002bba..78c75edc723 100644 --- a/docs/cli/hooks.md +++ b/docs/cli/hooks.md @@ -15,7 +15,7 @@ Running `openclaw hooks` with no subcommand is equivalent to `openclaw hooks lis Related: - Hooks: [Hooks](/automation/hooks) -- Plugin hooks: [Plugin hooks](/plugins/architecture-internals#provider-runtime-hooks) +- Plugin hooks: [Plugin hooks](/plugins/hooks) ## List All Hooks diff --git a/docs/concepts/agent-loop.md b/docs/concepts/agent-loop.md index ba4458e4290..979f6fc37b4 100644 --- a/docs/concepts/agent-loop.md +++ b/docs/concepts/agent-loop.md @@ -112,7 +112,7 @@ Hook decision rules for outbound/tool guards: - `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers. - `message_sending`: `{ cancel: false }` is a no-op and does not clear a prior cancel. -See [Plugin hooks](/plugins/architecture-internals#provider-runtime-hooks) for the hook API and registration details. +See [Plugin hooks](/plugins/hooks) for the hook API and registration details. Harnesses may adapt these hooks differently. The Codex app-server harness keeps OpenClaw plugin hooks as the compatibility contract for documented mirrored diff --git a/docs/docs.json b/docs/docs.json index bd31fb890fc..f5da4c35a00 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -1158,6 +1158,7 @@ "group": "Building plugins", "pages": [ "plugins/building-plugins", + "plugins/hooks", "plugins/sdk-channel-plugins", "plugins/sdk-provider-plugins", "plugins/sdk-migration" diff --git a/docs/plugins/building-plugins.md b/docs/plugins/building-plugins.md index 28e44aeccea..e4eb4d4de0a 100644 --- a/docs/plugins/building-plugins.md +++ b/docs/plugins/building-plugins.md @@ -33,7 +33,7 @@ falls back to npm automatically. Add a model provider (LLM, proxy, or custom endpoint) - + Register agent tools, event hooks, or services — continue below @@ -163,7 +163,8 @@ A single plugin can register any number of capabilities via the `api` object: | Embedded Pi extension | `api.registerEmbeddedExtensionFactory(...)` | [SDK Overview](/plugins/sdk-overview#registration-api) | | Agent tools | `api.registerTool(...)` | Below | | Custom commands | `api.registerCommand(...)` | [Entry Points](/plugins/sdk-entrypoints) | -| Event hooks | `api.registerHook(...)` | [Entry Points](/plugins/sdk-entrypoints) | +| Plugin hooks | `api.on(...)` | [Plugin hooks](/plugins/hooks) | +| Internal event hooks | `api.registerHook(...)` | [Entry Points](/plugins/sdk-entrypoints) | | HTTP routes | `api.registerHttpRoute(...)` | [Internals](/plugins/architecture-internals#gateway-http-routes) | | CLI subcommands | `api.registerCli(...)` | [Entry Points](/plugins/sdk-entrypoints) | @@ -197,7 +198,7 @@ If custom approval plumbing needs to detect that same bounded fallback case, prefer `isApprovalNotFoundError` from `openclaw/plugin-sdk/error-runtime` instead of matching approval-expiry strings manually. -See [SDK Overview hook decision semantics](/plugins/sdk-overview#hook-decision-semantics) for details. +See [Plugin hooks](/plugins/hooks) for examples and the hook reference. ## Registering agent tools diff --git a/docs/plugins/hooks.md b/docs/plugins/hooks.md new file mode 100644 index 00000000000..3a4cf69c129 --- /dev/null +++ b/docs/plugins/hooks.md @@ -0,0 +1,188 @@ +--- +summary: "Plugin hooks: intercept agent, tool, message, session, and Gateway lifecycle events" +title: "Plugin hooks" +read_when: + - You are building a plugin that needs before_tool_call, before_agent_reply, message hooks, or lifecycle hooks + - You need to block, rewrite, or require approval for tool calls from a plugin + - You are deciding between internal hooks and plugin hooks +--- + +Plugin hooks are in-process extension points for OpenClaw plugins. Use them +when a plugin needs to inspect or change agent runs, tool calls, message flow, +session lifecycle, subagent routing, installs, or Gateway startup. + +Use [internal hooks](/automation/hooks) instead when you want a small +operator-installed `HOOK.md` script for command and Gateway events such as +`/new`, `/reset`, `/stop`, `agent:bootstrap`, or `gateway:startup`. + +## Quick start + +Register typed plugin hooks with `api.on(...)` from your plugin entry: + +```typescript +import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; + +export default definePluginEntry({ + id: "tool-preflight", + name: "Tool Preflight", + register(api) { + api.on( + "before_tool_call", + async (event) => { + if (event.toolName !== "web_search") { + return; + } + + return { + requireApproval: { + title: "Run web search", + description: `Allow search query: ${String(event.params.query ?? "")}`, + severity: "info", + timeoutMs: 60_000, + timeoutBehavior: "deny", + }, + }; + }, + { priority: 50 }, + ); + }, +}); +``` + +Hook handlers run sequentially in descending `priority`. Same-priority hooks +keep registration order. + +## Common hooks + +| Hook | Use it for | +| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| `before_tool_call` | Rewrite tool params, block execution, or request user approval before a tool runs. | +| `after_tool_call` | Observe tool results, errors, and duration after execution. | +| `before_prompt_build` | Add dynamic context or system prompt text before the model call. | +| `before_model_resolve` | Override provider or model before session messages are loaded. | +| `before_agent_reply` | Short-circuit the model turn with a synthetic reply or silence. | +| `llm_input` / `llm_output` | Observe provider input/output for conversation-aware plugins. | +| `agent_end` | Observe final messages, success state, and run duration. | +| `message_received` | Observe inbound channel messages after channel parsing. | +| `message_sending` | Rewrite or cancel outbound channel messages. | +| `message_sent` | Observe outbound delivery success or failure. | +| `session_start` / `session_end` | Track session lifecycle boundaries. | +| `before_compaction` / `after_compaction` | Observe or annotate compaction cycles. | +| `subagent_spawning` / `subagent_delivery_target` / `subagent_spawned` / `subagent_ended` | Coordinate subagent routing and completion delivery. | +| `gateway_start` / `gateway_stop` | Start or stop plugin services with the Gateway. | +| `before_install` | Inspect skill or plugin install scans and optionally block. | + +## Tool call policy + +`before_tool_call` receives: + +- `event.toolName` +- `event.params` +- optional `event.runId` +- optional `event.toolCallId` +- context fields such as `ctx.agentId`, `ctx.sessionKey`, `ctx.sessionId`, and + diagnostic `ctx.trace` + +It can return: + +```typescript +type BeforeToolCallResult = { + params?: Record; + block?: boolean; + blockReason?: string; + requireApproval?: { + title: string; + description: string; + severity?: "info" | "warning" | "critical"; + timeoutMs?: number; + timeoutBehavior?: "allow" | "deny"; + onResolution?: (decision: string) => Promise | void; + }; +}; +``` + +Rules: + +- `block: true` is terminal and skips lower-priority handlers. +- `block: false` is treated as no decision. +- `params` rewrites the tool parameters for execution. +- `requireApproval` pauses the agent run and asks the user through plugin + approvals. The `/approve` command can approve both exec and plugin approvals. +- A lower-priority `block: true` can still block after a higher-priority hook + requested approval. + +## Prompt and model hooks + +Use the phase-specific hooks for new plugins: + +- `before_model_resolve`: receives only the current prompt and attachment + metadata. Return `providerOverride` or `modelOverride`. +- `before_prompt_build`: receives the current prompt and session messages. + Return `prependContext`, `systemPrompt`, `prependSystemContext`, or + `appendSystemContext`. + +`before_agent_start` remains for compatibility. Prefer the explicit hooks above +so your plugin does not depend on a legacy combined phase. + +Non-bundled plugins that need `llm_input`, `llm_output`, or `agent_end` must set: + +```json +{ + "plugins": { + "entries": { + "my-plugin": { + "hooks": { + "allowConversationAccess": true + } + } + } + } +} +``` + +Prompt-mutating hooks can be disabled per plugin with +`plugins.entries..hooks.allowPromptInjection=false`. + +## Message hooks + +Use message hooks for channel-level routing and delivery policy: + +- `message_received`: observe inbound content, sender, `threadId`, and metadata. +- `message_sending`: rewrite `content` or return `{ cancel: true }`. +- `message_sent`: observe final success or failure. + +Prefer typed `threadId` and `replyToId` fields before using channel-specific +metadata. + +Decision rules: + +- `message_sending` with `cancel: true` is terminal. +- `message_sending` with `cancel: false` is treated as no decision. +- Rewritten `content` continues to lower-priority hooks unless a later hook + cancels delivery. + +## Install hooks + +`before_install` runs after the built-in scan for skill and plugin installs. +Return additional findings or `{ block: true, blockReason }` to stop the +install. + +`block: true` is terminal. `block: false` is treated as no decision. + +## Gateway lifecycle + +Use `gateway_start` for plugin services that need Gateway-owned state. The +context exposes `ctx.config`, `ctx.workspaceDir`, and `ctx.getCron?.()` for +cron inspection and updates. Use `gateway_stop` to clean up long-running +resources. + +Do not rely on the internal `gateway:startup` hook for plugin-owned runtime +services. + +## Related + +- [Building plugins](/plugins/building-plugins) +- [Plugin SDK overview](/plugins/sdk-overview) +- [Plugin entry points](/plugins/sdk-entrypoints) +- [Internal hooks](/automation/hooks) +- [Plugin architecture internals](/plugins/architecture-internals) diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 7c99488a92f..be6ae861eda 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -17,6 +17,7 @@ reference for **what to import** and **what you can register**. - First plugin? Start with [Building plugins](/plugins/building-plugins). - Channel plugin? See [Channel plugins](/plugins/sdk-channel-plugins). - Provider plugin? See [Provider plugins](/plugins/sdk-provider-plugins). +- Tool or lifecycle hook plugin? See [Plugin hooks](/plugins/hooks). ## Import convention @@ -229,6 +230,9 @@ AI CLI backend such as `codex-cli`. | `api.on(hookName, handler, opts?)` | Typed lifecycle hook | | `api.onConversationBindingResolved(handler)` | Conversation binding callback | +See [Plugin hooks](/plugins/hooks) for examples, common hook names, and guard +semantics. + ### Hook decision semantics - `before_tool_call`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped. diff --git a/docs/start/hubs.md b/docs/start/hubs.md index ced82a9796d..c84af3f75a6 100644 --- a/docs/start/hubs.md +++ b/docs/start/hubs.md @@ -164,6 +164,7 @@ Use these hubs to discover every page, including deep dives and reference docs t - [Plugins overview](/tools/plugin) - [Building plugins](/plugins/building-plugins) +- [Plugin hooks](/plugins/hooks) - [Plugin manifest](/plugins/manifest) - [Agent tools](/plugins/building-plugins#registering-agent-tools) - [Plugin bundles](/plugins/bundles)