#16618: feat: bridge message lifecycle hooks to workspace hook system
docs
agents
size: M
## Summary
Bridges three message lifecycle events to the internal workspace hook system via `triggerInternalHook`, enabling workspace hooks (HOOK.md-based) to receive per-message events.
## Events Added
| Event | When | Modifying |
|-------|------|-----------|
| `message:received` | Inbound message parsed | No |
| `message:before` | Before agent processes | Yes (prependContext, systemPrompt) |
| `message:sent` | After response sent | No |
## Motivation
Workspace hooks currently only receive `command:new`, `command:stop`, and `gateway:startup`. Per-message events are dispatched through the plugin hook runner but never bridged to workspace hooks, making them inaccessible without building a full plugin.
This enables use cases like:
- Real-time transcript publishing to NATS/message buses
- Context injection from external knowledge stores
- Sentiment detection and adaptive prompting
- Slack thread context injection (eliminates tool call round-trip)
## Implementation
- Added `"message"` to `InternalHookEventType` union
- Added `InternalHookResult` interface with `prependContext` and `systemPrompt` fields
- Modified `triggerInternalHook` to collect and merge handler results (`prependContext` concatenated with `\n\n`, `systemPrompt` last-wins)
- Three `triggerInternalHook` calls added to the inbound message handling path:
- `dispatch-from-config.ts`: fire-and-forget `message:received` for channel messages
- `attempt.ts`: awaited `message:received`, modifying `message:before`, fire-and-forget `message:sent` for agent runner
- `message:before` follows the same modifying pattern as `before_agent_start` in the embedded runner
- No loader changes needed — `src/hooks/loader.ts` already registers handlers for arbitrary event strings from HOOK.md metadata
## Test Plan
- [x] Added 6 new unit tests for result-merging behavior in `internal-hooks.test.ts`
- [x] `pnpm build` passes (pre-existing type errors in discord/memory modules unchanged)
- [x] `pnpm test:fast` — all 4247 tests pass, 0 regressions
- [x] Linter: 0 warnings, 0 errors
Closes #7067, #15442, #14735, #12914, #12867, #8807
Partially addresses #7724, #11485, #10502
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR bridges three message lifecycle events (`message:received`, `message:before`, `message:sent`) from the agent runtime into the workspace hook system, enabling HOOK.md-based workspace hooks to observe and modify per-message events. The implementation adds result-merging capabilities to `triggerInternalHook` for modifying hooks, properly handles `prependContext` concatenation and last-wins `systemPrompt` merging, and includes comprehensive test coverage.
The core changes are well-structured and follow existing patterns from the plugin hook system. The `message:before` hook correctly applies both `prependContext` and `systemPrompt` modifications to the session. Test coverage is thorough with 6 new tests covering result merging behavior.
One issue was previously flagged but has since been resolved - the `systemPrompt` field from `message:before` is now properly applied via `applySystemPromptOverrideToSession` (attempt.ts:916-921).
<h3>Confidence Score: 4/5</h3>
- Safe to merge with one minor data mapping issue in the embedded runner path
- The implementation is solid with proper type safety, comprehensive tests (all 4247 passing), and follows existing patterns. The main concern is the incorrect `from` field mapping in attempt.ts which uses `messageTo` instead of sender info, though this doesn't affect core functionality since the field is only used for hook context. The `systemPrompt` application that was previously flagged has been correctly implemented.
- src/agents/pi-embedded-runner/run/attempt.ts requires attention for sender info mapping
<sub>Last reviewed commit: 1a6da0d</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#19922: feat(hooks): add message:received and message:sent hook events
by NOVA-Openclaw · 2026-02-18
84.0%
#13415: fix(hooks): bridge agent_end events to internal/workspace hooks
by mcaxtr · 2026-02-10
83.1%
#19565: feat: add agent lifecycle hook events (session, message, error)
by tag-assistant · 2026-02-17
82.6%
#7580: feat: add message:received internal hook with prompt injection
by rodrigoschott · 2026-02-03
81.8%
#11597: feat(hooks): implement message:received hook
by gnufoo · 2026-02-08
81.1%
#7545: feat(hooks): add message:received hook for pre-turn automation
by wangtian24 · 2026-02-02
80.3%
#15577: feat(hooks): add message:preprocessed hook event
by heybeaux · 2026-02-13
79.0%
#22624: feat(plugins): add before_context_send hook and model routing via b...
by davidrudduck · 2026-02-21
78.6%
#23019: fix(hooks): use globalThis singleton for internal hooks handlers Map
by karmafeast · 2026-02-21
78.4%
#11732: feat(plugins): add injectMessages to before_agent_start hook
by antra-tess · 2026-02-08
78.4%