← Back to PRs

#20580: feat(hooks): bridge after_tool_call to internal hook handler system

by CryptoKrad open 2026-02-19 03:34 View on GitHub →
agents size: S
## Summary Bridges the plugin hook system's `after_tool_call` event to the internal hook handler system, so plugins in `~/.openclaw/hooks/<plugin>/handler.ts` can observe tool executions. Discussed in #20575. Related to #20570. --- ## Problem OpenClaw has two hook systems that don't talk to each other: **Plugin Hook System** (internal): fires `after_tool_call` inside the agent pipeline with full tool context. **Internal Hook Handler System** (external, `~/.openclaw/hooks/`): never receives tool events — blind to everything happening inside a session. The result: memory, observability, and audit plugins can't record what tools ran, what the inputs were, or what came back. ## Changes ### `src/hooks/internal-hooks.ts` - Add `"tool"` to `InternalHookEventType` - Add `ToolAfterCallHookContext` + `ToolAfterCallHookEvent` types - Add `isToolAfterCallEvent()` type guard ### `src/agents/pi-embedded-subscribe.handlers.tools.ts` - After existing plugin hook fires → bridge to `triggerInternalHook("tool", "after_call", ...)` (fire-and-forget, never blocks execution) ### `src/agents/pi-tool-definition-adapter.ts` - Same bridge in both success path and error path ### `src/hooks/internal-hooks.test.ts` - 6 new tests: `isToolAfterCallEvent` guard (true/false cases) + handler triggering ## Behaviour The bridge fires **unconditionally** — it does not require any plugin hooks to be registered. It is always fire-and-forget and can never block or throw into the agent pipeline. Payload delivered to external handlers: ```ts event.type // "tool" event.action // "after_call" event.context // { toolName, params, result?, error?, durationMs? } ``` ## Usage Example ```ts // ~/.openclaw/hooks/my-memory/handler.ts import type { HookHandler } from '../../src/hooks/hooks.js'; import { isToolAfterCallEvent } from '../../src/hooks/internal-hooks.js'; const handler: HookHandler = async (event) => { if (isToolAfterCallEvent(event)) { const { toolName, params, result, error, durationMs } = event.context; await recordObservation({ toolName, params, result, error, durationMs }); } }; export default handler; ``` HOOK.md metadata to subscribe: ```yaml metadata: { "openclaw": { "events": ["tool:after_call"] } } ``` ## Test Results ``` Test Files 1 passed (1) Tests 36 passed (36) ← 6 new, 30 existing ``` Build: `pnpm build` — clean, zero errors. <!-- greptile_comment --> <h3>Greptile Summary</h3> Bridges the plugin hook system's `after_tool_call` event to the internal hook handler system. This enables external plugins in `~/.openclaw/hooks/<plugin>/handler.ts` to observe tool executions that were previously only visible to internal plugin hooks. - Adds `"tool"` event type to `InternalHookEventType` union with `ToolAfterCallHookContext` and `ToolAfterCallHookEvent` types - Adds `isToolAfterCallEvent()` type guard for type-safe event filtering - Bridges tool execution events in both `pi-embedded-subscribe.handlers.tools.ts` (embedded agent path) and `pi-tool-definition-adapter.ts` (tool definition adapter paths) - Fire-and-forget pattern ensures hooks never block tool execution - Comprehensive test coverage with 6 new tests validating type guards and handler triggering <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk - The implementation follows established patterns in the codebase, uses fire-and-forget to prevent blocking, includes comprehensive test coverage (6 new tests, all passing), and maintains type safety throughout. The changes are isolated to the hook system with no breaking changes to existing APIs. - No files require special attention <sub>Last reviewed commit: 81572ae</sub> <!-- greptile_other_comments_section --> <sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub> <!-- /greptile_comment -->

Most Similar PRs