feat: bridge message lifecycle hooks to workspace hook system#16618
feat: bridge message lifecycle hooks to workspace hook system#16618DarlingtonDeveloper wants to merge 3 commits intoopenclaw:mainfrom
Conversation
|
Context protection is crucial. We use aggressive pruning (keepLastAssistants=1) and memoryFlush at 60K. How are you measuring impact? |
|
Duplicate of #12584 and missing tests passing and docs changes |
| if (messageBeforeResult?.prependContext) { | ||
| effectivePrompt = `${messageBeforeResult.prependContext}\n\n${effectivePrompt}`; | ||
| log.debug( | ||
| `message:before hook prepended context (${messageBeforeResult.prependContext.length} chars)`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
systemPrompt field from messageBeforeResult is not used. Only prependContext is checked and applied, but InternalHookResult also includes systemPrompt which is merged in triggerInternalHook. Should apply systemPrompt if present, similar to how the plugin before_agent_start hook uses it (line 883-887).
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 910:915
Comment:
`systemPrompt` field from `messageBeforeResult` is not used. Only `prependContext` is checked and applied, but `InternalHookResult` also includes `systemPrompt` which is merged in `triggerInternalHook`. Should apply `systemPrompt` if present, similar to how the plugin `before_agent_start` hook uses it (line 883-887).
How can I resolve this? If you propose a fix, please make it concise.| from: "", | ||
| content: typeof params.prompt === "string" ? params.prompt : "", | ||
| channel: (params.messageChannel ?? params.messageProvider ?? "").toLowerCase(), | ||
| senderId: undefined, | ||
| senderName: undefined, | ||
| commandSource: (params.messageChannel ?? params.messageProvider ?? "").toLowerCase(), |
There was a problem hiding this comment.
Sender info hardcoded as empty instead of using available params. params.senderId, params.senderName exist but are set to from: "", senderId: undefined, senderName: undefined. Compare with dispatch-from-config.ts:204-215 which properly uses ctx.From, ctx.SenderId, ctx.SenderName.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 847:852
Comment:
Sender info hardcoded as empty instead of using available params. `params.senderId`, `params.senderName` exist but are set to `from: ""`, `senderId: undefined`, `senderName: undefined`. Compare with `dispatch-from-config.ts:204-215` which properly uses `ctx.From`, `ctx.SenderId`, `ctx.SenderName`.
How can I resolve this? If you propose a fix, please make it concise.|
Good question. We're running a multi-agent swarm where context pressure is a real constraint, the orchestrator runs a long-lived WhatsApp/Slack sessions that accumulate fast. Current approach:
Impact measurement is mostly qualitative right now — did the agent lose track of what it was doing post-compaction? Did it recover from memory files correctly? We've had 6 compactions today and the main failure mode is losing in-flight conversation state during the ~30s compaction window. The hooks in this PR are partly motivated by this — |
|
|
|
Re: duplicate of #12584 — I don't think these are duplicates. Different hook systems. #12584 wires message events through the plugin hook runner ( Plugin hooks and workspace hooks (HOOK.md-based) are separate dispatch paths. A workspace hook registered for The two PRs are complementary, not competing. Happy to coordinate if there's a preferred approach to unifying them. Re: tests and docs, that's a fair point, will add those. |
bfc1ccb to
f92900f
Compare
| "received", | ||
| params.sessionKey || params.sessionId || "", | ||
| { | ||
| from: params.messageTo ?? "", |
There was a problem hiding this comment.
from field uses messageTo which is the delivery target, not the sender. Should use sender info params like senderId/senderName/senderE164 or create a from field from available sender params.
Compare with dispatch-from-config.ts:204 which correctly uses ctx.From.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 847:847
Comment:
`from` field uses `messageTo` which is the delivery target, not the sender. Should use sender info params like `senderId`/`senderName`/`senderE164` or create a `from` field from available sender params.
Compare with dispatch-from-config.ts:204 which correctly uses `ctx.From`.
How can I resolve this? If you propose a fix, please make it concise.Add message:received, message:before, and message:sent internal hook events so workspace hooks (HOOK.md) can react to individual messages without being a full plugin. Extends triggerInternalHook to collect and merge handler results (prependContext concatenated, systemPrompt last-wins). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add tests for message:received, message:before, and message:sent hook events covering event shape, result merging, and no-op behavior. Document the three new message lifecycle events in hooks docs (EN + zh-CN) with context field tables, return value semantics, and example handlers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…fields Address review feedback: - Wire messageBeforeResult.systemPrompt through applySystemPromptOverrideToSession - Use params.senderId/senderName/messageTo in message:received context Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1a6da0d to
1db1c82
Compare
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
message:receivedmessage:beforemessage:sentMotivation
Workspace hooks currently only receive
command:new,command:stop, andgateway: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:
Implementation
"message"toInternalHookEventTypeunionInternalHookResultinterface withprependContextandsystemPromptfieldstriggerInternalHookto collect and merge handler results (prependContextconcatenated with\n\n,systemPromptlast-wins)triggerInternalHookcalls added to the inbound message handling path:dispatch-from-config.ts: fire-and-forgetmessage:receivedfor channel messagesattempt.ts: awaitedmessage:received, modifyingmessage:before, fire-and-forgetmessage:sentfor agent runnermessage:beforefollows the same modifying pattern asbefore_agent_startin the embedded runnersrc/hooks/loader.tsalready registers handlers for arbitrary event strings from HOOK.md metadataTest Plan
internal-hooks.test.tspnpm buildpasses (pre-existing type errors in discord/memory modules unchanged)pnpm test:fast— all 4247 tests pass, 0 regressionsCloses #7067, #15442, #14735, #12914, #12867, #8807
Partially addresses #7724, #11485, #10502
Greptile Summary
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 totriggerInternalHookfor modifying hooks, properly handlesprependContextconcatenation and last-winssystemPromptmerging, and includes comprehensive test coverage.The core changes are well-structured and follow existing patterns from the plugin hook system. The
message:beforehook correctly applies bothprependContextandsystemPromptmodifications 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
systemPromptfield frommessage:beforeis now properly applied viaapplySystemPromptOverrideToSession(attempt.ts:916-921).Confidence Score: 4/5
fromfield mapping in attempt.ts which usesmessageToinstead of sender info, though this doesn't affect core functionality since the field is only used for hook context. ThesystemPromptapplication that was previously flagged has been correctly implemented.Last reviewed commit: 1a6da0d