feat(plugins): add harness tool result middleware (#71021)

This commit is contained in:
Vincent Koc
2026-04-24 12:39:13 -07:00
committed by GitHub
parent e471d40942
commit 47f6a98909
38 changed files with 738 additions and 86 deletions

View File

@@ -1,2 +1,2 @@
c4a62f081d0b9fcfd5e76a843547411bba0fdc129c1c143e7f4c4f6294b040b9 plugin-sdk-api-baseline.json
a62c9aea45d5694a851380ff6b35b7fb2ffd9fc4dfa3f0c567a8e1c97094475e plugin-sdk-api-baseline.jsonl
b758a1c5503c08325113e0d6c9f1ac2db5a5fd9992a3902706ebe0f0dbbc1213 plugin-sdk-api-baseline.json
2c9d0a00e526dcd47d131261b8ceddd8e59faa8530b129d108a3721a4cbcbea7 plugin-sdk-api-baseline.jsonl

View File

@@ -160,7 +160,7 @@ A single plugin can register any number of capabilities via the `api` object:
| Video generation | `api.registerVideoGenerationProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Web fetch | `api.registerWebFetchProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Web search | `api.registerWebSearchProvider(...)` | [Provider Plugins](/plugins/sdk-provider-plugins#step-5-add-extra-capabilities) |
| Embedded Pi extension | `api.registerEmbeddedExtensionFactory(...)` | [SDK Overview](/plugins/sdk-overview#registration-api) |
| Tool-result middleware | `api.registerAgentToolResultMiddleware(...)` | [SDK Overview](/plugins/sdk-overview#registration-api) |
| Agent tools | `api.registerTool(...)` | Below |
| Custom commands | `api.registerCommand(...)` | [Entry Points](/plugins/sdk-entrypoints) |
| Plugin hooks | `api.on(...)` | [Plugin hooks](/plugins/hooks) |
@@ -170,10 +170,11 @@ A single plugin can register any number of capabilities via the `api` object:
For the full registration API, see [SDK Overview](/plugins/sdk-overview#registration-api).
Use `api.registerEmbeddedExtensionFactory(...)` when a plugin needs Pi-native
embedded-runner hooks such as async `tool_result` rewriting before the final
tool result message is emitted. Prefer regular OpenClaw plugin hooks when the
work does not need Pi extension timing.
Use `api.registerAgentToolResultMiddleware(...)` when a plugin needs async
tool-result rewriting before the model sees the output. Declare the targeted
harnesses in `contracts.agentToolResultMiddleware`, for example
`["pi", "codex-app-server"]`. Prefer regular OpenClaw plugin hooks when the
work does not need pre-model tool-result timing.
If your plugin registers custom gateway RPC methods, keep them on a
plugin-specific prefix. Core admin namespaces (`config.*`,

View File

@@ -25,11 +25,11 @@ These are in-process OpenClaw hooks, not Codex `hooks.json` command hooks:
- `before_message_write` for mirrored transcript records
- `agent_end`
Bundled plugins can also register a Codex app-server extension factory to add
async `tool_result` middleware. That middleware runs for OpenClaw dynamic tools
after OpenClaw executes the tool and before the result is returned to Codex. It
is separate from the public `tool_result_persist` plugin hook, which transforms
OpenClaw-owned transcript tool-result writes.
Plugins can also register harness-neutral tool-result middleware to rewrite
OpenClaw dynamic tool results after OpenClaw executes the tool and before the
result is returned to Codex. This is separate from the public
`tool_result_persist` plugin hook, which transforms OpenClaw-owned transcript
tool-result writes.
The harness is off by default. New configs should keep OpenAI model refs
canonical as `openai/gpt-*` and explicitly force

View File

@@ -396,7 +396,7 @@ read without importing the plugin runtime.
```json
{
"contracts": {
"embeddedExtensionFactories": ["pi"],
"agentToolResultMiddleware": ["pi", "codex-app-server"],
"externalAuthProviders": ["acme-ai"],
"speechProviders": ["openai"],
"realtimeTranscriptionProviders": ["openai"],
@@ -414,20 +414,26 @@ read without importing the plugin runtime.
Each list is optional:
| Field | Type | What it means |
| -------------------------------- | ---------- | ----------------------------------------------------------------- |
| `embeddedExtensionFactories` | `string[]` | Embedded runtime ids a bundled plugin may register factories for. |
| `externalAuthProviders` | `string[]` | Provider ids whose external auth profile hook this plugin owns. |
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
| `memoryEmbeddingProviders` | `string[]` | Memory embedding provider ids this plugin owns. |
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
| `tools` | `string[]` | Agent tool names this plugin owns for bundled contract checks. |
| Field | Type | What it means |
| -------------------------------- | ---------- | ---------------------------------------------------------------- |
| `embeddedExtensionFactories` | `string[]` | Deprecated embedded extension factory ids. |
| `agentToolResultMiddleware` | `string[]` | Harness ids this plugin may register tool-result middleware for. |
| `externalAuthProviders` | `string[]` | Provider ids whose external auth profile hook this plugin owns. |
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
| `memoryEmbeddingProviders` | `string[]` | Memory embedding provider ids this plugin owns. |
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
| `tools` | `string[]` | Agent tool names this plugin owns for bundled contract checks. |
`contracts.embeddedExtensionFactories` is retained for bundled compatibility
code that still needs direct Pi embedded-runner events. New tool-result
transforms should declare `contracts.agentToolResultMiddleware` and register
with `api.registerAgentToolResultMiddleware(...)` instead.
Provider plugins that implement `resolveExternalAuthProfiles` should declare
`contracts.externalAuthProviders`. Plugins without the declaration still run

View File

@@ -144,14 +144,20 @@ OpenClaw requires Codex app-server `0.118.0` or newer. The Codex plugin checks
the app-server initialize handshake and blocks older or unversioned servers so
OpenClaw only runs against the protocol surface it has been tested with.
### Codex app-server tool-result middleware
### Tool-result middleware
Bundled plugins can also attach Codex app-server-specific `tool_result`
middleware through `api.registerCodexAppServerExtensionFactory(...)` when their
manifest declares `contracts.embeddedExtensionFactories: ["codex-app-server"]`.
This is the trusted-plugin seam for async tool-result transforms that need to
run inside the native Codex harness before the tool output is projected back
into the OpenClaw transcript.
Plugins can attach harness-neutral tool-result middleware through
`api.registerAgentToolResultMiddleware(...)` when their manifest declares the
targeted harness ids in `contracts.agentToolResultMiddleware`. This is the seam
for async tool-result transforms that must run before PI or Codex feeds tool
output back into the model.
Legacy bundled plugins can still use
`api.registerCodexAppServerExtensionFactory(...)` for Codex app-server-only
middleware, but new result transforms should use the harness-neutral API.
The Pi-only `api.registerEmbeddedExtensionFactory(...)` hook is deprecated for
tool-result transforms; keep it only for bundled compatibility code that still
needs direct Pi embedded-runner events.
### Native Codex harness mode

View File

@@ -5,6 +5,7 @@ sidebarTitle: "Migrate to SDK"
read_when:
- You see the OPENCLAW_PLUGIN_SDK_COMPAT_DEPRECATED warning
- You see the OPENCLAW_EXTENSION_API_DEPRECATED warning
- You use api.registerEmbeddedExtensionFactory
- You are updating a plugin to the modern plugin architecture
- You maintain an external OpenClaw plugin
---
@@ -23,8 +24,10 @@ anything they needed from a single entry point:
new plugin architecture was being built.
- **`openclaw/extension-api`** — a bridge that gave plugins direct access to
host-side helpers like the embedded agent runner.
- **`api.registerEmbeddedExtensionFactory(...)`** — a Pi-only bundled extension
hook that could observe embedded-runner events such as `tool_result`.
Both surfaces are now **deprecated**. They still work at runtime, but new
These surfaces are now **deprecated**. They still work at runtime, but new
plugins must not use them, and existing plugins should migrate before the next
major release removes them.
@@ -87,6 +90,41 @@ releases.
## How to migrate
<Steps>
<Step title="Migrate Pi tool-result extensions to middleware">
Replace Pi-only `api.registerEmbeddedExtensionFactory(...)` tool-result
handlers with harness-neutral middleware.
```typescript
// Before: Pi-only compatibility hook
api.registerEmbeddedExtensionFactory((pi) => {
pi.on("tool_result", async (event) => {
return compactToolResult(event);
});
});
// After: Pi and Codex app-server dynamic tools
api.registerAgentToolResultMiddleware(async (event) => {
return compactToolResult(event);
}, {
harnesses: ["pi", "codex-app-server"],
});
```
Update the plugin manifest at the same time:
```json
{
"contracts": {
"agentToolResultMiddleware": ["pi", "codex-app-server"]
}
}
```
Keep `contracts.embeddedExtensionFactories` only for bundled compatibility
code that still needs direct Pi embedded-runner events.
</Step>
<Step title="Migrate approval-native handlers to capability facts">
Approval-capable channel plugins now expose native approval behavior through
`approvalCapability.nativeRuntime` plus the shared runtime-context registry.

View File

@@ -99,7 +99,8 @@ methods:
| `api.registerCli(registrar, opts?)` | CLI subcommand |
| `api.registerService(service)` | Background service |
| `api.registerInteractiveHandler(registration)` | Interactive handler |
| `api.registerEmbeddedExtensionFactory(factory)` | Pi embedded-runner extension factory |
| `api.registerAgentToolResultMiddleware(...)` | Harness tool-result middleware |
| `api.registerEmbeddedExtensionFactory(factory)` | Deprecated PI extension factory |
| `api.registerMemoryPromptSupplement(builder)` | Additive memory-adjacent prompt section |
| `api.registerMemoryCorpusSupplement(adapter)` | Additive memory search/read corpus |
@@ -110,15 +111,22 @@ methods:
plugin-owned methods.
</Note>
<Accordion title="When to use registerEmbeddedExtensionFactory">
Use `api.registerEmbeddedExtensionFactory(...)` when a plugin needs Pi-native
event timing during OpenClaw embedded runs — for example async `tool_result`
rewrites that must happen before the final tool-result message is emitted.
<Accordion title="When to use tool-result middleware">
Use `api.registerAgentToolResultMiddleware(...)` when a plugin needs to
rewrite a tool result after execution and before the harness feeds that
result back into the model. This is the harness-neutral seam for async output
reducers such as tokenjuice.
This is a bundled-plugin seam today: only bundled plugins may register one,
and they must declare `contracts.embeddedExtensionFactories: ["pi"]` in
`openclaw.plugin.json`. Keep normal OpenClaw plugin hooks for everything that
does not require that lower-level seam.
Plugins must declare `contracts.agentToolResultMiddleware` for each targeted
harness, for example `["pi", "codex-app-server"]`. Keep normal OpenClaw
plugin hooks for work that does not need pre-model tool-result timing.
</Accordion>
<Accordion title="Legacy Pi extension factories">
`api.registerEmbeddedExtensionFactory(...)` is deprecated. It remains a
compatibility seam for bundled plugins that still need direct Pi
embedded-runner events. New tool-result transforms should use
`api.registerAgentToolResultMiddleware(...)` instead.
</Accordion>
### Gateway discovery registration

View File

@@ -13,8 +13,9 @@ tool results after the command has already run.
It changes the returned `tool_result`, not the command itself. Tokenjuice does
not rewrite shell input, rerun commands, or change exit codes.
Today this applies to Pi embedded runs, where tokenjuice hooks the embedded
`tool_result` path and trims the output that goes back into the session.
Today this applies to PI embedded runs and OpenClaw dynamic tools in the Codex
app-server harness. Tokenjuice hooks OpenClaw's tool-result middleware and
trims the output before it goes back into the active harness session.
## Enable the plugin