feat(plugins): add full conversation context to before_tool_call hook#58079
feat(plugins): add full conversation context to before_tool_call hook#58079Reapor-Yurnero wants to merge 10 commits into
Conversation
Greptile SummaryThis PR extends the
Confidence Score: 4/5Safe to merge with minor polishing; no data loss or security regression introduced. The implementation is clean, additive, and well-tested. The two findings are P2: the live-array reference for messages is a documentation/contract mismatch unlikely to cause runtime failures in practice (JavaScript is single-threaded and tool execution is sequential), and the systemPrompt undefined spread inconsistency only surfaces in a very narrow early-initialization window. Neither blocks merge but both are worth addressing before the plugin API stabilises. src/agents/pi-embedded-runner/run/attempt.ts (messages provider at line 874) and src/agents/pi-tools.before-tool-call.ts (systemPrompt spread guard at line 208).
|
| Filename | Overview |
|---|---|
| src/agents/pi-tools.before-tool-call.ts | Adds getSystemPrompt/getMessages/getTools lazy getters to HookContext and expands runBeforeToolCallHook payload. Two minor issues: undefined spread inconsistency for systemPrompt and live array reference semantics for messages. |
| src/agents/pi-embedded-runner/run/attempt.ts | Wires beforeToolCallContext (lazy getters for systemPrompt, messages, tools) into tools and hooks. The messages provider at line 874 hands a live mutable array to the hook payload rather than a snapshot. |
| src/agents/pi-embedded-runner/tool-split.ts | Threads optional hookContext through to toToolDefinitions so MCP/LSP tools receive full conversation context in before_tool_call. Clean, minimal change. |
| src/agents/pi-tool-definition-adapter.ts | Adds optional hookContext parameter to toToolDefinitions and passes it when invoking runBeforeToolCallHook for unwrapped tools. Consistent with existing pattern. |
| src/agents/pi-tools.ts | Exposes beforeToolCallContext as an optional parameter on createOpenClawCodingTools and spreads it into the wrapToolWithBeforeToolCallHook call. Omits identity/session fields as documented. |
| src/plugins/types.ts | Adds PluginHookToolInfo type and three optional fields (systemPrompt, messages, tools) to PluginHookBeforeToolCallEvent. Fully additive, backward-compatible. |
| src/agents/pi-tools.before-tool-call.context.test.ts | New unit test validating that systemPrompt/messages/tools are forwarded through runBeforeToolCallHook. Does not cover the getSystemPrompt-returns-undefined edge case. |
| src/agents/pi-tool-definition-adapter.after-tool-call.test.ts | Adds a test verifying that hookContext fields are passed through toToolDefinitions correctly. Straightforward coverage. |
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/pi-tools.before-tool-call.ts
Line: 208-210
Comment:
**`systemPrompt: undefined` spreads key with undefined value**
The guard `args.ctx?.getSystemPrompt && { ... }` checks only that the *function* exists, not that it returns a defined value. If `getSystemPrompt()` returns `undefined` (e.g. during the brief window before `beforeToolCallSystemPrompt` is first assigned), this spreads `{ systemPrompt: undefined }` into the payload. In-process plugins would see the key present (with value `undefined`); JSON-serialized external plugins would silently drop the key. This creates an inconsistent API surface between plugin delivery modes.
A consistent pattern would be:
```suggestion
...(args.ctx?.getSystemPrompt?.() !== undefined && { systemPrompt: args.ctx.getSystemPrompt() }),
...(args.ctx?.getMessages && { messages: args.ctx.getMessages() }),
...(args.ctx?.getTools && { tools: args.ctx.getTools() }),
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/agents/pi-embedded-runner/run/attempt.ts
Line: 874
Comment:
**`messages` is a live mutable reference, not a snapshot**
`activeSession.messages` is the runtime's internal array, updated in place as the conversation progresses. By the time an async plugin hook returns (e.g. after an HTTP round-trip to a guardrail service), the array may already contain new entries that weren't present when the hook was invoked. The `PluginHookBeforeToolCallEvent` type documents this field as "Current conversation snapshot," but the live reference violates that contract for any plugin that either (a) stores the reference or (b) awaits an async operation before reading it.
A shallow clone at the time the getter is called would satisfy the "snapshot" contract without any functional overhead in the common case:
```typescript
beforeToolCallMessagesProvider = () => [...activeSession.messages];
```
Note: the earlier provider set at line 736–737 (`buildSessionContext().messages`) already returns a freshly built array on each call, so it is consistent with snapshot semantics. This line should match that behaviour.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "fix(plugins): propagate before_tool_call..." | Re-trigger Greptile
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 804e632c6a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44ef0440f8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
This pull request has been automatically marked as stale due to inactivity. |
|
Codex review: found issues before merge. What this changes: The PR adds optional systemPrompt, messages, and tools fields to before_tool_call hook payloads and threads lazy context through embedded-runner tool wrappers with focused tests. Maintainer follow-up before merge: This is a public plugin API and security-sensitive hook-context change on an unmergeable, unmodifiable contributor branch; a maintainer should decide whether to request a rebase, ask for a replacement PR, or narrow the API before automation attempts code changes. Security review: Security review needs attention: The diff needs security attention because PR head can silently bypass before_tool_call guardrail/approval hooks when snapshot cloning throws. Review findings:
Review detailsBest possible solution: Pursue a rebased or replacement implementation that updates the current hook-types contract, docs, changelog, and contract tests, while preserving fail-closed hook behavior or dropping only uncloneable optional snapshot fields without skipping before_tool_call handlers. Do we have a high-confidence way to reproduce the issue? Yes for the blocking review finding: on PR head, make getMessages/getTools return a value structuredClone cannot clone and run a tool call with a before_tool_call hook; the hookEvent construction throws, the catch logs, and execution falls through as allowed. Current-main behavior was verified by source inspection rather than executing tests because this was a read-only review. Is this the best way to solve the issue? No as-is. The narrow additive hook-context direction is plausible, but the current PR branch is stale, unmergeable, missing current hook contract placement, and can bypass the very policy hooks it is meant to empower. Full review comments:
Overall correctness: patch is incorrect Security concerns:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against e47a7448e90f. |
Summary
Describe the problem and fix in 2–5 bullets:
before_tool_callonly exposedtoolName/params, which is not enough for an external policy / guardrail plugin to evaluate a tool call in context to look out for prompt injections / policy-violated actions etc.before_tool_callnow optionally includessystemPrompt,messages, andtools, wired from the embedded runner into the existing hook payload.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
systemPrompt,messages, andtoolsfrombefore_tool_call.Diagram (if applicable)
Security Impact (required)
Yes/No) YesYes/No) NoYes/No) NoYes/No) NoYes/No) YesYes, explain risk + mitigation:before_tool_callplugin can observe by adding context (systemPrompt,messages,tools).Repro + Verification
Environment
pnpmdev buildopenai/gpt-5.4openclaw plugins install --link, usingbeforeToolCallonlySteps
before_tool_calland forwards the payload to a local dummy HTTP endpoint.Expected
systemPrompt,messages, andtoolsalongside the existingtoolName/params.Actual
Evidence
Attach at least one:
End-to-end local test with an external standalone plugin posting hook payloads to a dummy HTTP serve
Gateway trace from the same run:
Automated Verification
pnpm build,pnpm test:contracts:plugins,pnpm checkpassespnpm testfailed exactly the same as the upstream main current state.codex review --base upstream/mainand incorporated the suggestion to propagate before_tool_call context to bundled MCP/LSP tools so the hook behavior is consistent across various tool sources.Human Verification (required)
What you personally verified (not just CI), and how:
before_tool_callcontext forwardingReview Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No) YesYes/No) NoYes/No) NoRisks and Mitigations
before_tool_callcan now observe more runtime context than before.