Skip to content

Commit de4b8d8

Browse files
authored
feat(plugins): allow installed trusted policy contracts
Allow explicitly enabled installed plugins to register declared trusted tool policies and agent tool result middleware, with trusted policy ids scoped by plugin owner.\n\nVerification covered targeted plugin/agent tests, typecheck, build, lint, local autoreview, and a Blacksmith Testbox runtime proof (tbx_01ktr1nq0rhq47fjkwrepm7fd3).
1 parent 52bc2a1 commit de4b8d8

28 files changed

Lines changed: 1153 additions & 102 deletions

docs/cli/plugins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ openclaw plugins inspect <id> --runtime
417417
openclaw plugins inspect <id> --json
418418
```
419419

420-
Inspect shows identity, load status, source, manifest capabilities, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support without importing plugin runtime by default. Add `--runtime` to load the plugin module and include registered hooks, tools, commands, services, gateway methods, and HTTP routes. Runtime inspection reports missing plugin dependencies directly; installs and repairs stay in `openclaw plugins install`, `openclaw plugins update`, and `openclaw doctor --fix`.
420+
Inspect shows identity, load status, source, manifest capabilities, policy flags, diagnostics, install metadata, bundle capabilities, and any detected MCP or LSP server support without importing plugin runtime by default. JSON output includes the plugin manifest contracts, such as `contracts.agentToolResultMiddleware` and `contracts.trustedToolPolicies`, so operators can audit trusted-surface declarations before enabling or restarting a plugin. Add `--runtime` to load the plugin module and include registered hooks, tools, commands, services, gateway methods, and HTTP routes. Runtime inspection reports missing plugin dependencies directly; installs and repairs stay in `openclaw plugins install`, `openclaw plugins update`, and `openclaw doctor --fix`.
421421

422422
Plugin-owned CLI commands are usually installed as root `openclaw` command groups, but plugins may also register nested commands under a core parent such as `openclaw nodes`. After `inspect --runtime` shows a command under `cliCommands`, run it at the listed path; for example a plugin that registers `demo-git` can be verified with `openclaw demo-git ping`.
423423

docs/plugins/building-plugins.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ local proof.
106106
eagerly loading every plugin runtime. Set `activation.onStartup`
107107
intentionally. This example starts on Gateway startup.
108108

109+
Host-trusted plugin surfaces are also manifest-gated and require explicit
110+
enablement for installed plugins. If an installed plugin registers
111+
`api.registerAgentToolResultMiddleware(...)`, declare each target runtime in
112+
`contracts.agentToolResultMiddleware`. If it registers
113+
`api.registerTrustedToolPolicy(...)`, declare each policy id in
114+
`contracts.trustedToolPolicies`. These declarations keep install-time
115+
inspection and runtime registration aligned.
116+
109117
For every manifest field, see [Plugin manifest](/plugins/manifest).
110118

111119
</Step>

docs/plugins/hooks.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,17 @@ See [Plugin permission requests](/plugins/plugin-permission-requests) for
227227
approval routing, decision behavior, and when to use `requireApproval` instead
228228
of optional tools or exec approvals.
229229

230-
Bundled plugins that need host-level policy can register trusted tool policies
231-
with `api.registerTrustedToolPolicy(...)`. These run before ordinary
232-
`before_tool_call` hooks and before external plugin decisions. Use them only
230+
Plugins that need host-level policy can register trusted tool policies with
231+
`api.registerTrustedToolPolicy(...)`. These run before ordinary
232+
`before_tool_call` hooks and before normal hook decisions. Bundled trusted
233+
policies run first; installed-plugin trusted policies run next in plugin-load
234+
order; ordinary `before_tool_call` hooks run after them. Bundled plugins keep
235+
the existing trusted-policy path. Installed plugins must be explicitly enabled
236+
and declare every policy id in `contracts.trustedToolPolicies`; undeclared ids
237+
are rejected before registration. Policy ids are scoped to the registering
238+
plugin, so different plugins may reuse the same local id. Use this tier only
233239
for host-trusted gates such as workspace policy, budget enforcement, or
234-
reserved workflow safety. External plugins should use normal `before_tool_call`
235-
hooks.
240+
reserved workflow safety.
236241

237242
### Exec environment hook
238243

docs/plugins/manifest.md

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,7 @@ read without importing the plugin runtime.
631631
{
632632
"contracts": {
633633
"agentToolResultMiddleware": ["openclaw", "codex"],
634+
"trustedToolPolicies": ["workflow-budget"],
634635
"externalAuthProviders": ["acme-ai"],
635636
"embeddingProviders": ["openai-compatible"],
636637
"speechProviders": ["openai"],
@@ -651,32 +652,41 @@ read without importing the plugin runtime.
651652

652653
Each list is optional:
653654

654-
| Field | Type | What it means |
655-
| -------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
656-
| `embeddedExtensionFactories` | `string[]` | Codex app-server extension factory ids, currently `codex-app-server`. |
657-
| `agentToolResultMiddleware` | `string[]` | Runtime ids a bundled plugin may register tool-result middleware for. |
658-
| `externalAuthProviders` | `string[]` | Provider ids whose external auth profile hook this plugin owns. |
659-
| `embeddingProviders` | `string[]` | General embedding provider ids this plugin owns for reusable vector embedding use, including memory. |
660-
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
661-
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
662-
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
663-
| `memoryEmbeddingProviders` | `string[]` | Deprecated memory-specific embedding provider ids this plugin owns. |
664-
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
665-
| `transcriptSourceProviders` | `string[]` | Transcript source provider ids this plugin owns. |
666-
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
667-
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
668-
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
669-
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
670-
| `migrationProviders` | `string[]` | Import provider ids this plugin owns for `openclaw migrate`. |
671-
| `gatewayMethodDispatch` | `string[]` | Reserved entitlement for authenticated plugin HTTP routes that dispatch Gateway methods in-process. |
672-
| `tools` | `string[]` | Agent tool names this plugin owns. |
655+
| Field | Type | What it means |
656+
| -------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
657+
| `embeddedExtensionFactories` | `string[]` | Codex app-server extension factory ids, currently `codex-app-server`. |
658+
| `agentToolResultMiddleware` | `string[]` | Runtime ids this plugin may register tool-result middleware for. |
659+
| `trustedToolPolicies` | `string[]` | Plugin-local trusted pre-tool policy ids an installed plugin may register. Bundled plugins may register policies without this field. |
660+
| `externalAuthProviders` | `string[]` | Provider ids whose external auth profile hook this plugin owns. |
661+
| `embeddingProviders` | `string[]` | General embedding provider ids this plugin owns for reusable vector embedding use, including memory. |
662+
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
663+
| `realtimeTranscriptionProviders` | `string[]` | Realtime-transcription provider ids this plugin owns. |
664+
| `realtimeVoiceProviders` | `string[]` | Realtime-voice provider ids this plugin owns. |
665+
| `memoryEmbeddingProviders` | `string[]` | Deprecated memory-specific embedding provider ids this plugin owns. |
666+
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
667+
| `transcriptSourceProviders` | `string[]` | Transcript source provider ids this plugin owns. |
668+
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
669+
| `videoGenerationProviders` | `string[]` | Video-generation provider ids this plugin owns. |
670+
| `webFetchProviders` | `string[]` | Web-fetch provider ids this plugin owns. |
671+
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
672+
| `migrationProviders` | `string[]` | Import provider ids this plugin owns for `openclaw migrate`. |
673+
| `gatewayMethodDispatch` | `string[]` | Reserved entitlement for authenticated plugin HTTP routes that dispatch Gateway methods in-process. |
674+
| `tools` | `string[]` | Agent tool names this plugin owns. |
673675

674676
`contracts.embeddedExtensionFactories` is retained for bundled Codex
675677
app-server-only extension factories. Bundled tool-result transforms should
676678
declare `contracts.agentToolResultMiddleware` and register with
677-
`api.registerAgentToolResultMiddleware(...)` instead. External plugins cannot
678-
register tool-result middleware because the seam can rewrite high-trust tool
679-
output before the model sees it.
679+
`api.registerAgentToolResultMiddleware(...)` instead. Installed plugins may use
680+
the same middleware seam only when explicitly enabled and only for runtimes they
681+
declare in `contracts.agentToolResultMiddleware`.
682+
683+
Installed plugins that need the host-trusted pre-tool policy tier must declare
684+
each registered local id in `contracts.trustedToolPolicies` and be explicitly
685+
enabled. Bundled plugins keep the existing trusted-policy path, but installed
686+
plugins with undeclared policy ids are rejected before registration. Policy ids
687+
are scoped to the registering plugin, so two plugins may both declare and
688+
register `workflow-budget`; a single plugin may not register the same local id
689+
twice.
680690

681691
Runtime `api.registerTool(...)` registrations must match `contracts.tools`.
682692
Tool discovery uses this list to load only the plugin runtimes that can own the

docs/plugins/sdk-agent-harness.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,12 @@ Codex `0.124.0`, while pinning OpenClaw to the newer tested stable line.
160160

161161
### Tool-result middleware
162162

163-
Bundled plugins can attach runtime-neutral tool-result middleware through
163+
Bundled plugins and explicitly enabled installed plugins with matching manifest
164+
contracts can attach runtime-neutral tool-result middleware through
164165
`api.registerAgentToolResultMiddleware(...)` when their manifest declares the
165166
targeted runtime ids in `contracts.agentToolResultMiddleware`. This trusted
166-
seam is for async tool-result transforms that must run before OpenClaw or Codex feeds
167-
tool output back into the model.
167+
seam is for async tool-result transforms that must run before OpenClaw or Codex
168+
feeds tool output back into the model.
168169

169170
Legacy bundled plugins can still use
170171
`api.registerCodexAppServerExtensionFactory(...)` for Codex app-server-only

docs/plugins/sdk-migration.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,8 +318,10 @@ releases.
318318
}
319319
```
320320

321-
External plugins cannot register tool-result middleware because it can
322-
rewrite high-trust tool output before the model sees it.
321+
Installed plugins can also register tool-result middleware when they are
322+
explicitly enabled and declare every targeted runtime in
323+
`contracts.agentToolResultMiddleware`. Undeclared installed middleware
324+
registrations are rejected.
323325

324326
</Step>
325327

docs/plugins/sdk-overview.md

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ plugins.
187187
| ------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
188188
| `api.session.state.registerSessionExtension(...)` | Plugin-owned, JSON-compatible session state projected through Gateway sessions |
189189
| `api.session.workflow.enqueueNextTurnInjection(...)` | Durable exactly-once context injected into the next agent turn for one session |
190-
| `api.registerTrustedToolPolicy(...)` | Bundled/trusted pre-plugin tool policy that can block or rewrite tool params |
190+
| `api.registerTrustedToolPolicy(...)` | Manifest-gated trusted pre-plugin tool policy that can block or rewrite tool params |
191191
| `api.registerToolMetadata(...)` | Tool catalog display metadata without changing the tool implementation |
192192
| `api.registerCommand(...)` | Scoped plugin commands; command results can set `continueAgent: true`; Discord native commands support `descriptionLocalizations` |
193193
| `api.session.controls.registerControlUiDescriptor(...)` | Control UI contribution descriptors for session, tool, run, or settings surfaces |
@@ -235,7 +235,10 @@ The contracts intentionally split authority:
235235
- External plugins can own session extensions, UI descriptors, commands, tool
236236
metadata, next-turn injections, and normal hooks.
237237
- Trusted tool policies run before ordinary `before_tool_call` hooks and are
238-
bundled-only because they participate in host safety policy.
238+
host-trusted. Bundled policies run first; installed-plugin policies require
239+
explicit enablement plus their local ids in
240+
`contracts.trustedToolPolicies`, and run next in plugin-load order. Policy ids
241+
are scoped to the registering plugin.
239242
- Reserved command ownership is bundled-only. External plugins should use their
240243
own command names or aliases.
241244
- `allowPromptInjection=false` disables prompt-mutating hooks including
@@ -260,16 +263,18 @@ Examples of non-Plan consumers:
260263
</Note>
261264

262265
<Accordion title="When to use tool-result middleware">
263-
Bundled plugins can use `api.registerAgentToolResultMiddleware(...)` when
266+
Bundled plugins and explicitly enabled installed plugins with matching
267+
manifest contracts can use `api.registerAgentToolResultMiddleware(...)` when
264268
they need to rewrite a tool result after execution and before the runtime
265269
feeds that result back into the model. This is the trusted runtime-neutral
266270
seam for async output reducers such as tokenjuice.
267271

268-
Bundled plugins must declare `contracts.agentToolResultMiddleware` for each
269-
targeted runtime, for example `["openclaw", "codex"]`. External plugins
270-
cannot register this middleware; keep normal OpenClaw plugin hooks for work
271-
that does not need pre-model tool-result timing. The old embedded-runner-only
272-
extension factory registration path has been removed.
272+
Plugins must declare `contracts.agentToolResultMiddleware` for each targeted
273+
runtime, for example `["openclaw", "codex"]`. Installed plugins without that
274+
contract, or without explicit enablement, cannot register this middleware; keep
275+
normal OpenClaw plugin hooks for work that does not need pre-model tool-result
276+
timing. The old
277+
embedded-runner-only extension factory registration path has been removed.
273278
</Accordion>
274279

275280
### Gateway discovery registration

extensions/qa-lab/src/scenario-catalog.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,10 @@ describe("qa scenario catalog", () => {
412412
"kitchen-sink-realtime-voice-provider",
413413
);
414414
expect(config?.expectedAdversarialDiagnostics).toContain(
415-
"only bundled plugins can register agent tool result middleware",
415+
"agent tool result middleware must be a function",
416+
);
417+
expect(config?.expectedAdversarialDiagnostics).toContain(
418+
"trusted tool policy registration requires id, description, and evaluate()",
416419
);
417420
expect(config?.expectedAdversarialDiagnostics).toContain(
418421
"control UI descriptor registration requires id, surface, label, and valid optional fields",

qa/scenarios/plugins/kitchen-sink-live-openai.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ execution:
8787
livePrompt: "Kitchen Sink OpenAI marker. Reply exactly: KITCHEN-SINK-OPENAI-OK"
8888
expectedAdversarialDiagnostics:
8989
- agent event subscription registration requires id and handle
90-
- only bundled plugins can register agent tool result middleware
90+
- agent tool result middleware must be a function
9191
- agent harness "kitchen-sink-agent-harness" registration missing required runtime methods
9292
- channel "kitchen-sink-channel-probe" registration missing required config helpers
9393
- cli registration missing explicit commands metadata
@@ -99,14 +99,14 @@ execution:
9999
- "http route registration missing or invalid auth: /kitchen-sink/http-route"
100100
- "plugin must declare contracts.embeddingProviders for adapter: kitchen-sink-embedding-provider"
101101
- "plugin must own memory slot or declare contracts.memoryEmbeddingProviders for adapter: kitchen-sink-memory-embedding-provider"
102+
- "trusted tool policy registration requires id, description, and evaluate()"
102103
- memory prompt supplement registration missing builder
103104
- model catalog provider registration missing provider
104105
- node invoke policy registration missing commands
105106
- session extension registration requires namespace and description
106107
- session scheduler job registration requires unique id, sessionKey, and kind
107108
- "plugin must declare contracts.tools for: kitchen-sink-tool"
108109
- tool metadata registration missing toolName
109-
- only bundled plugins can register trusted tool policies
110110
```
111111
112112
```yaml qa-flow

0 commit comments

Comments
 (0)