Skip to content

Commit d78512b

Browse files
authored
Refactor: centralize native approval lifecycle assembly (#62135)
Merged via squash. Prepared head SHA: b7c20a7 Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com> Reviewed-by: @gumadeiras
1 parent 4108901 commit d78512b

128 files changed

Lines changed: 8838 additions & 3994 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,12 @@ Docs: https://docs.openclaw.ai
8787
- Providers/Mistral: send `reasoning_effort` for `mistral/mistral-small-latest` (Mistral Small 4) with thinking-level mapping, and mark the catalog entry as reasoning-capable so adjustable reasoning matches Mistral’s Chat Completions API. (#62162) Thanks @neeravmakwana.
8888
- OpenAI TTS/Groq: send `wav` to Groq-compatible speech endpoints, honor explicit `responseFormat` overrides on OpenAI-compatible paths, and only mark voice-note output as voice-compatible when the actual format is `opus`. (#62233) Thanks @neeravmakwana.
8989
- BlueBubbles/network: respect explicit private-network opt-out for loopback and private `serverUrl` values across account resolution, status probes, monitor startup, and attachment downloads, while keeping public-host attachment hostname pinning intact. (#59373) Thanks @jpreagan.
90+
<<<<<<< HEAD
9091
- Agents/heartbeat: keep heartbeat runs pinned to the main session so active subagent transcripts are not overwritten by heartbeat status messages. (#61803) thanks @100yenadmin.
9192
- Agents/compaction: stop compaction-wait aborts from re-entering prompt failover and replaying completed tool turns. (#62600) Thanks @i-dentifier.
93+
=======
94+
- Approvals/runtime: move native approval lifecycle assembly into shared core bootstrap/runtime seams driven by channel capabilities and runtime contexts, and remove the legacy bundled approval fallback wiring. (#62135) Thanks @gumadeiras.
95+
>>>>>>> 367f6afaf1 (Approvals: finish capability cutover and Matrix parity)
9296

9397
## 2026.4.5
9498

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
5e28885aeddb1c2e73040c88b503d584bbcd871c6941fd1ebf7f22ceac3477a6 plugin-sdk-api-baseline.json
2-
c8bbc54b51588b6b9aecabb3fcf02ecb69867c8ac527b65d5ec3bc5c6288057a plugin-sdk-api-baseline.jsonl
1+
7abdd7f9977c44bb8799f6c1047aa2a025217bbdc46c42329e46796e9d08b02a plugin-sdk-api-baseline.json
2+
aca10bdd74bae01a8a2210c745ac2a0583b83ff8035aa2764b817967cb3a0b02 plugin-sdk-api-baseline.jsonl

docs/channels/matrix.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -880,21 +880,23 @@ See [Pairing](/channels/pairing) for the shared DM pairing flow and storage layo
880880

881881
## Exec approvals
882882

883-
Matrix can act as an exec approval client for a Matrix account.
883+
Matrix can act as a native approval client for a Matrix account. The native
884+
DM/channel routing knobs still live under exec approval config:
884885

885886
- `channels.matrix.execApprovals.enabled`
886887
- `channels.matrix.execApprovals.approvers` (optional; falls back to `channels.matrix.dm.allowFrom`)
887888
- `channels.matrix.execApprovals.target` (`dm` | `channel` | `both`, default: `dm`)
888889
- `channels.matrix.execApprovals.agentFilter`
889890
- `channels.matrix.execApprovals.sessionFilter`
890891

891-
Approvers must be Matrix user IDs such as `@owner:example.org`. Matrix auto-enables native exec approvals when `enabled` is unset or `"auto"` and at least one approver can be resolved, either from `execApprovals.approvers` or from `channels.matrix.dm.allowFrom`. Set `enabled: false` to disable Matrix as a native approval client explicitly. Approval requests otherwise fall back to other configured approval routes or the exec approval fallback policy.
892+
Approvers must be Matrix user IDs such as `@owner:example.org`. Matrix auto-enables native approvals when `enabled` is unset or `"auto"` and at least one approver can be resolved. Exec approvals use `execApprovals.approvers` first and can fall back to `channels.matrix.dm.allowFrom`. Plugin approvals authorize through `channels.matrix.dm.allowFrom`. Set `enabled: false` to disable Matrix as a native approval client explicitly. Approval requests otherwise fall back to other configured approval routes or the approval fallback policy.
892893

893-
Native Matrix routing is exec-only today:
894+
Matrix native routing now supports both approval kinds:
894895

895-
- `channels.matrix.execApprovals.*` controls native DM/channel routing for exec approvals only.
896-
- Plugin approvals still use shared same-chat `/approve` plus any configured `approvals.plugin` forwarding.
897-
- Matrix can still reuse `channels.matrix.dm.allowFrom` for plugin-approval authorization when it can infer approvers safely, but it does not expose a separate native plugin-approval DM/channel fanout path.
896+
- `channels.matrix.execApprovals.*` controls the native DM/channel fanout mode for Matrix approval prompts.
897+
- Exec approvals use the exec approver set from `execApprovals.approvers` or `channels.matrix.dm.allowFrom`.
898+
- Plugin approvals use the Matrix DM allowlist from `channels.matrix.dm.allowFrom`.
899+
- Matrix reaction shortcuts and message updates apply to both exec and plugin approvals.
898900

899901
Delivery rules:
900902

@@ -910,9 +912,9 @@ Matrix approval prompts seed reaction shortcuts on the primary approval message:
910912

911913
Approvers can react on that message or use the fallback slash commands: `/approve <id> allow-once`, `/approve <id> allow-always`, or `/approve <id> deny`.
912914

913-
Only resolved approvers can approve or deny. Channel delivery includes the command text, so only enable `channel` or `both` in trusted rooms.
915+
Only resolved approvers can approve or deny. For exec approvals, channel delivery includes the command text, so only enable `channel` or `both` in trusted rooms.
914916

915-
Matrix approval prompts reuse the shared core approval planner. The Matrix-specific native surface is transport only for exec approvals: room/DM routing and message send/update/delete behavior.
917+
Matrix approval prompts reuse the shared core approval planner. The Matrix-specific native surface handles room/DM routing, reactions, and message send/update/delete behavior for both exec and plugin approvals.
916918

917919
Per-account override:
918920

docs/plugins/architecture.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ authoring plugins:
11341134
`openclaw/plugin-sdk/channel-config-schema`,
11351135
`openclaw/plugin-sdk/telegram-command-config`,
11361136
`openclaw/plugin-sdk/channel-policy`,
1137+
`openclaw/plugin-sdk/approval-handler-runtime`,
11371138
`openclaw/plugin-sdk/approval-runtime`,
11381139
`openclaw/plugin-sdk/config-runtime`,
11391140
`openclaw/plugin-sdk/infra-runtime`,
@@ -1152,9 +1153,9 @@ authoring plugins:
11521153
assistant-visible-text stripping, markdown render/chunking helpers, redaction
11531154
helpers, directive-tag helpers, and safe-text utilities.
11541155
- Approval-specific channel seams should prefer one `approvalCapability`
1155-
contract on the plugin. Core then reads approval auth, delivery, render, and
1156-
native-routing behavior through that one capability instead of mixing
1157-
approval behavior into unrelated plugin fields.
1156+
contract on the plugin. Core then reads approval auth, delivery, render,
1157+
native-routing, and lazy native-handler behavior through that one capability
1158+
instead of mixing approval behavior into unrelated plugin fields.
11581159
- `openclaw/plugin-sdk/channel-runtime` is deprecated and remains only as a
11591160
compatibility shim for older plugins. New code should import the narrower
11601161
generic primitives instead, and repo code should not add new imports of the

docs/plugins/sdk-channel-plugins.md

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,22 +60,34 @@ Most channel plugins do not need approval-specific code.
6060

6161
- Core owns same-chat `/approve`, shared approval button payloads, and generic fallback delivery.
6262
- Prefer one `approvalCapability` object on the channel plugin when the channel needs approval-specific behavior.
63+
- `ChannelPlugin.approvals` is removed. Put approval delivery/native/render/auth facts on `approvalCapability`.
64+
- `plugin.auth` is login/logout only; core no longer reads approval auth hooks from that object.
6365
- `approvalCapability.authorizeActorAction` and `approvalCapability.getActionAvailabilityState` are the canonical approval-auth seam.
64-
- If your channel exposes native exec approvals, implement `approvalCapability.getActionAvailabilityState` even when the native transport lives entirely under `approvalCapability.native`. Core uses that availability hook to distinguish `enabled` vs `disabled`, decide whether the initiating channel supports native approvals, and include the channel in native-client fallback guidance.
66+
- Use `approvalCapability.getActionAvailabilityState` for same-chat approval auth availability.
67+
- If your channel exposes native exec approvals, use `approvalCapability.getExecInitiatingSurfaceState` for the initiating-surface/native-client state when it differs from same-chat approval auth. Core uses that exec-specific hook to distinguish `enabled` vs `disabled`, decide whether the initiating channel supports native exec approvals, and include the channel in native-client fallback guidance. `createApproverRestrictedNativeApprovalCapability(...)` fills this in for the common case.
6568
- Use `outbound.shouldSuppressLocalPayloadPrompt` or `outbound.beforeDeliverPayload` for channel-specific payload lifecycle behavior such as hiding duplicate local approval prompts or sending typing indicators before delivery.
6669
- Use `approvalCapability.delivery` only for native approval routing or fallback suppression.
70+
- Use `approvalCapability.nativeRuntime` for channel-owned native approval facts. Keep it lazy on hot channel entrypoints with `createLazyChannelApprovalNativeRuntimeAdapter(...)`, which can import your runtime module on demand while still letting core assemble the approval lifecycle.
6771
- Use `approvalCapability.render` only when a channel truly needs custom approval payloads instead of the shared renderer.
6872
- Use `approvalCapability.describeExecApprovalSetup` when the channel wants the disabled-path reply to explain the exact config knobs needed to enable native exec approvals. The hook receives `{ channel, channelLabel, accountId }`; named-account channels should render account-scoped paths such as `channels.<channel>.accounts.<id>.execApprovals.*` instead of top-level defaults.
6973
- If a channel can infer stable owner-like DM identities from existing config, use `createResolvedApproverActionAuthAdapter` from `openclaw/plugin-sdk/approval-runtime` to restrict same-chat `/approve` without adding approval-specific core logic.
70-
- If a channel needs native approval delivery, keep channel code focused on target normalization and transport hooks. Use `createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`, `createChannelApproverDmTargetResolver`, `createApproverRestrictedNativeApprovalCapability`, and `createChannelNativeApprovalRuntime` from `openclaw/plugin-sdk/approval-runtime` so core owns request filtering, routing, dedupe, expiry, and gateway subscription.
74+
- If a channel needs native approval delivery, keep channel code focused on target normalization plus transport/presentation facts. Use `createChannelExecApprovalProfile`, `createChannelNativeOriginTargetResolver`, `createChannelApproverDmTargetResolver`, and `createApproverRestrictedNativeApprovalCapability` from `openclaw/plugin-sdk/approval-runtime`. Put the channel-specific facts behind `approvalCapability.nativeRuntime`, ideally via `createChannelApprovalNativeRuntimeAdapter(...)` or `createLazyChannelApprovalNativeRuntimeAdapter(...)`, so core can assemble the handler and own request filtering, routing, dedupe, expiry, gateway subscription, and routed-elsewhere notices. `nativeRuntime` is split into a few smaller seams:
75+
- `availability` — whether the account is configured and whether a request should be handled
76+
- `presentation` — map the shared approval view model into pending/resolved/expired native payloads or final actions
77+
- `transport` — prepare targets plus send/update/delete native approval messages
78+
- `interactions` — optional bind/unbind/clear-action hooks for native buttons or reactions
79+
- `observe` — optional delivery diagnostics hooks
80+
- If the channel needs runtime-owned objects such as a client, token, Bolt app, or webhook receiver, register them through `openclaw/plugin-sdk/channel-runtime-context`. The generic runtime-context registry lets core bootstrap capability-driven handlers from channel startup state without adding approval-specific wrapper glue.
81+
- Reach for the lower-level `createChannelApprovalHandler` or `createChannelNativeApprovalRuntime` only when the capability-driven seam is not expressive enough yet.
7182
- Native approval channels must route both `accountId` and `approvalKind` through those helpers. `accountId` keeps multi-account approval policy scoped to the right bot account, and `approvalKind` keeps exec vs plugin approval behavior available to the channel without hardcoded branches in core.
83+
- Core now owns approval reroute notices too. Channel plugins should not send their own "approval went to DMs / another channel" follow-up messages from `createChannelNativeApprovalRuntime`; instead, expose accurate origin + approver-DM routing through the shared approval capability helpers and let core aggregate actual deliveries before posting any notice back to the initiating chat.
7284
- Preserve the delivered approval id kind end-to-end. Native clients should not
7385
guess or rewrite exec vs plugin approval routing from channel-local state.
7486
- Different approval kinds can intentionally expose different native surfaces.
7587
Current bundled examples:
7688
- Slack keeps native approval routing available for both exec and plugin ids.
77-
- Matrix keeps native DM/channel routing for exec approvals only and leaves
78-
plugin approvals on the shared same-chat `/approve` path.
89+
- Matrix keeps the same native DM/channel routing and reaction UX for exec
90+
and plugin approvals, while still letting auth differ by approval kind.
7991
- `createApproverRestrictedNativeApprovalAdapter` still exists as a compatibility wrapper, but new code should prefer the capability builder and expose `approvalCapability` on the plugin.
8092

8193
For hot channel entrypoints, prefer the narrower runtime subpaths when you only
@@ -84,8 +96,10 @@ need one part of that family:
8496
- `openclaw/plugin-sdk/approval-auth-runtime`
8597
- `openclaw/plugin-sdk/approval-client-runtime`
8698
- `openclaw/plugin-sdk/approval-delivery-runtime`
99+
- `openclaw/plugin-sdk/approval-handler-runtime`
87100
- `openclaw/plugin-sdk/approval-native-runtime`
88101
- `openclaw/plugin-sdk/approval-reply-runtime`
102+
- `openclaw/plugin-sdk/channel-runtime-context`
89103

90104
Likewise, prefer `openclaw/plugin-sdk/setup-runtime`,
91105
`openclaw/plugin-sdk/setup-adapter-runtime`,

docs/plugins/sdk-migration.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,32 @@ Current bundled provider examples:
6767
## How to migrate
6868

6969
<Steps>
70+
<Step title="Migrate approval-native handlers to capability facts">
71+
Approval-capable channel plugins now expose native approval behavior through
72+
`approvalCapability.nativeRuntime` plus the shared runtime-context registry.
73+
74+
Key changes:
75+
76+
- Replace `approvalCapability.handler.loadRuntime(...)` with
77+
`approvalCapability.nativeRuntime`
78+
- Move approval-specific auth/delivery off legacy `plugin.auth` /
79+
`plugin.approvals` wiring and onto `approvalCapability`
80+
- `ChannelPlugin.approvals` has been removed from the public channel-plugin
81+
contract; move delivery/native/render fields onto `approvalCapability`
82+
- `plugin.auth` remains for channel login/logout flows only; approval auth
83+
hooks there are no longer read by core
84+
- Register channel-owned runtime objects such as clients, tokens, or Bolt
85+
apps through `openclaw/plugin-sdk/channel-runtime-context`
86+
- Do not send plugin-owned reroute notices from native approval handlers;
87+
core now owns routed-elsewhere notices from actual delivery results
88+
- When passing `channelRuntime` into `createChannelManager(...)`, provide a
89+
real `createPluginRuntime().channel` surface. Partial stubs are rejected.
90+
91+
See `/plugins/sdk-channel-plugins` for the current approval capability
92+
layout.
93+
94+
</Step>
95+
7096
<Step title="Audit Windows wrapper fallback behavior">
7197
If your plugin uses `openclaw/plugin-sdk/windows-spawn`, unresolved Windows
7298
`.cmd`/`.bat` wrappers now fail closed unless you explicitly pass
@@ -201,8 +227,10 @@ Current bundled provider examples:
201227
| `plugin-sdk/approval-auth-runtime` | Approval auth helpers | Approver resolution, same-chat action auth |
202228
| `plugin-sdk/approval-client-runtime` | Approval client helpers | Native exec approval profile/filter helpers |
203229
| `plugin-sdk/approval-delivery-runtime` | Approval delivery helpers | Native approval capability/delivery adapters |
230+
| `plugin-sdk/approval-handler-runtime` | Approval handler helpers | Shared approval handler runtime helpers, including capability-driven native approval loading |
204231
| `plugin-sdk/approval-native-runtime` | Approval target helpers | Native approval target/account binding helpers |
205232
| `plugin-sdk/approval-reply-runtime` | Approval reply helpers | Exec/plugin approval reply payload helpers |
233+
| `plugin-sdk/channel-runtime-context` | Channel runtime-context helpers | Generic channel runtime-context register/get/watch helpers |
206234
| `plugin-sdk/security-runtime` | Security helpers | Shared trust, DM gating, external-content, and secret-collection helpers |
207235
| `plugin-sdk/ssrf-policy` | SSRF policy helpers | Host allowlist and private-network policy helpers |
208236
| `plugin-sdk/ssrf-runtime` | SSRF runtime helpers | Pinned-dispatcher, guarded fetch, SSRF policy helpers |

docs/plugins/sdk-overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ explicitly promotes one as public.
151151
| `plugin-sdk/approval-auth-runtime` | Approver resolution and same-chat action-auth helpers |
152152
| `plugin-sdk/approval-client-runtime` | Native exec approval profile/filter helpers |
153153
| `plugin-sdk/approval-delivery-runtime` | Native approval capability/delivery adapters |
154+
| `plugin-sdk/approval-handler-runtime` | Shared approval handler runtime helpers, including capability-driven native approval loading |
154155
| `plugin-sdk/approval-native-runtime` | Native approval target + account-binding helpers |
155156
| `plugin-sdk/approval-reply-runtime` | Exec/plugin approval reply payload helpers |
156157
| `plugin-sdk/command-auth-native` | Native command auth + native session-target helpers |
@@ -172,6 +173,7 @@ explicitly promotes one as public.
172173
| --- | --- |
173174
| `plugin-sdk/runtime` | Broad runtime/logging/backup/plugin-install helpers |
174175
| `plugin-sdk/runtime-env` | Narrow runtime env, logger, timeout, retry, and backoff helpers |
176+
| `plugin-sdk/channel-runtime-context` | Generic channel runtime-context registration and lookup helpers |
175177
| `plugin-sdk/runtime-store` | `createPluginRuntimeStore` |
176178
| `plugin-sdk/plugin-runtime` | Shared plugin command/hook/http/interactive helpers |
177179
| `plugin-sdk/hook-runtime` | Shared webhook/internal hook pipeline helpers |

docs/tools/exec-approvals.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -557,8 +557,8 @@ Shared behavior:
557557
- Slack approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom`
558558
- Slack native buttons preserve approval id kind, so `plugin:` ids can resolve plugin approvals
559559
without a second Slack-local fallback layer
560-
- Matrix native DM/channel routing is exec-only; Matrix plugin approvals stay on the shared
561-
same-chat `/approve` and optional `approvals.plugin` forwarding paths
560+
- Matrix native DM/channel routing and reaction shortcuts handle both exec and plugin approvals;
561+
plugin authorization still comes from `channels.matrix.dm.allowFrom`
562562
- the requester does not need to be an approver
563563
- the originating chat can approve directly with `/approve` when that chat already supports commands and replies
564564
- native Discord approval buttons route by approval id kind: `plugin:` ids go

0 commit comments

Comments
 (0)