Skip to content

Commit 60397dd

Browse files
committed
feat(channels): add typed operator approval actions
Squash-rebased #103679 segment onto the durable-approval-registry tip on current main. Typed approval/command/select presentation actions replace raw-string inference across slack/telegram/discord/matrix/imessage/whatsapp, approval.resolve carries an explicit kind, and channel adapters map native callback envelopes through the typed action registry. Drift reconciliation: deprecated buildExecApprovalInteractiveReply assertions dropped (#104650 removed the shims); worker_environments bootstrap-column migration kept alongside the approval resolution_ref backfill; plugin-sdk API baseline regenerated. (cherry picked from commit 68765a5d39d2118c88a7a54d00387337912d4494) (cherry picked from commit 8642ac12af142e4b751f4f30d4b114615e7e5f66) (cherry picked from commit 036c4bc39499925fc03de16ec9302e346769350a) (cherry picked from commit 19dc350d6bc34e29a5169c6bc80971b0ad12adde) (cherry picked from commit fc978b0bad86aef421c79f6a211b25cc1b743c01) (cherry picked from commit 10de4d1ed5071f9be6ad1ee5d1e32c0fa8c9d11c) (cherry picked from commit 9a664ced1b1fa740172b258f355f1a82925ae41c) (cherry picked from commit c5ff69abbf444139e9e007bfa45beb0f00ffea54) (cherry picked from commit d466a80) (cherry picked from commit f5b4fe40dd5c961322f8553cc80b2fdfb3f6503e) (cherry picked from commit 7340b4749a4cc4c72f7a41cce1bc9cb550cae038) (cherry picked from commit a151f41808f23ae60b10305ccd2bc959b9169a86)
1 parent e9e7c37 commit 60397dd

178 files changed

Lines changed: 10695 additions & 1558 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.

apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10135,18 +10135,22 @@ public struct ApprovalGetResult: Codable, Sendable {
1013510135

1013610136
public struct ApprovalResolveParams: Codable, Sendable {
1013710137
public let id: String
10138+
public let kind: ApprovalKind
1013810139
public let decision: ApprovalDecision
1013910140

1014010141
public init(
1014110142
id: String,
10143+
kind: ApprovalKind,
1014210144
decision: ApprovalDecision)
1014310145
{
1014410146
self.id = id
10147+
self.kind = kind
1014510148
self.decision = decision
1014610149
}
1014710150

1014810151
private enum CodingKeys: String, CodingKey {
1014910152
case id
10153+
case kind
1015010154
case decision
1015110155
}
1015210156
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
e54bcec7d07d05d00010ca038776c16d33346b2b69c5c920f331b805584553a0 plugin-sdk-api-baseline.json
2-
af38688ddabaeeef60aa19917c2d96ab507afc2f05df49dc3a009f8f75bddb12 plugin-sdk-api-baseline.jsonl
1+
28669b8725f8641f4e3e1d00f1c120a78e20d8f3c7ed2055af77a58d2bd21c7d plugin-sdk-api-baseline.json
2+
d8bba86cc315f64d977d1b0f770e203b4e3531bbd2bc72aa7a834be154e5fd94 plugin-sdk-api-baseline.jsonl

docs/plugins/message-presentation.md

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,27 @@ type MessagePresentationBlock =
6969
};
7070

7171
type MessagePresentationAction =
72-
{ type: "command"; command: string } | { type: "callback"; value: string };
72+
| { type: "command"; command: string }
73+
| { type: "callback"; value: string }
74+
| {
75+
type: "approval";
76+
approvalId: string;
77+
approvalKind: "exec" | "plugin";
78+
decision: "allow-once" | "allow-always" | "deny";
79+
}
80+
| { type: "url"; url: string }
81+
| { type: "web-app"; url: string };
7382

7483
type MessagePresentationButton = {
7584
label: string;
7685
action?: MessagePresentationAction;
7786
/** Legacy callback value. Prefer action for new controls. */
7887
value?: string;
88+
/** @deprecated Use an action with type "url". */
7989
url?: string;
90+
/** @deprecated Use an action with type "web-app". */
8091
webApp?: { url: string };
81-
/** @deprecated Use webApp. Accepted for legacy JSON payloads only. */
92+
/** @deprecated Use an action with type "web-app". */
8293
web_app?: { url: string };
8394
priority?: number;
8495
disabled?: boolean;
@@ -88,7 +99,7 @@ type MessagePresentationButton = {
8899

89100
type MessagePresentationOption = {
90101
label: string;
91-
action?: MessagePresentationAction;
102+
action?: Extract<MessagePresentationAction, { type: "command" | "callback" }>;
92103
/** Legacy callback value. Prefer action for new controls. */
93104
value?: string;
94105
};
@@ -111,13 +122,18 @@ Button semantics:
111122
- `action.type: "callback"` carries opaque plugin data through the channel's
112123
interaction path. Channel plugins must not reinterpret callback data as slash
113124
commands.
125+
- `action.type: "approval"` identifies one durable operator approval, its
126+
explicit `exec` or `plugin` kind, and the requested decision. Channel plugins
127+
encode that action into a transport-private callback and resolve it through
128+
the approval service; they must not parse `/approve` command text or infer
129+
kind from the ID.
130+
- `action.type: "url"` opens a normal link.
131+
- `action.type: "web-app"` launches a channel-native web app.
114132
- `value` is the legacy opaque callback value. New controls should use `action`
115133
so channel plugins can map commands and callbacks without guessing from text.
116-
- `url` is a link button. It can exist without `value`.
117-
- `webApp` describes a channel-native web app button. Telegram renders this
118-
as `web_app` and only supports it in private chats. `web_app` is still
119-
accepted in loose JSON payloads for compatibility, but TypeScript producers
120-
should use `webApp`.
134+
- `url`, `webApp`, and `web_app` remain accepted as deprecated boundary inputs.
135+
Normalizers preserve these fields so renderers can distinguish shipped legacy
136+
semantics from explicit typed actions. New producers should use `action`.
121137
- `label` is required and is also used in text fallback.
122138
- `style` is advisory. Renderers should map unsupported styles to a safe
123139
default, not fail the send.
@@ -136,7 +152,7 @@ Button semantics:
136152

137153
Select semantics:
138154

139-
- `options[].action` has the same command/callback meaning as button `action`.
155+
- `options[].action` accepts only `command` or `callback`; approval and link actions are button-only.
140156
- `options[].value` is the legacy selected application value.
141157
- `placeholder` is advisory and may be ignored by channels without native
142158
select support.
@@ -193,8 +209,16 @@ Simple card:
193209
{
194210
"type": "buttons",
195211
"buttons": [
196-
{ "label": "Approve", "value": "deploy:approve", "style": "success" },
197-
{ "label": "Decline", "value": "deploy:decline", "style": "danger" }
212+
{
213+
"label": "Approve",
214+
"action": { "type": "callback", "value": "deploy:approve" },
215+
"style": "success"
216+
},
217+
{
218+
"label": "Decline",
219+
"action": { "type": "callback", "value": "deploy:decline" },
220+
"style": "danger"
221+
}
198222
]
199223
}
200224
]
@@ -209,7 +233,12 @@ URL-only link button:
209233
{ "type": "text", "text": "Release notes are ready." },
210234
{
211235
"type": "buttons",
212-
"buttons": [{ "label": "Open notes", "url": "https://example.com/release" }]
236+
"buttons": [
237+
{
238+
"label": "Open notes",
239+
"action": { "type": "url", "url": "https://example.com/release" }
240+
}
241+
]
213242
}
214243
]
215244
}
@@ -222,7 +251,12 @@ Telegram Mini App button:
222251
"blocks": [
223252
{
224253
"type": "buttons",
225-
"buttons": [{ "label": "Launch", "web_app": { "url": "https://example.com/app" } }]
254+
"buttons": [
255+
{
256+
"label": "Launch",
257+
"action": { "type": "web-app", "url": "https://example.com/app" }
258+
}
259+
]
226260
}
227261
]
228262
}
@@ -464,8 +498,12 @@ keeping opaque callback data private:
464498
copy the command and run it manually in the channel input.
465499
- **`callback`-typed actions** and legacy **`value`** fields render as
466500
label-only. The opaque callback value is not exposed in fallback text.
467-
- **`url` / `webApp`** buttons render the URL text alongside the button
468-
label, since the URL is user-facing.
501+
- **`approval`-typed actions** render label-only. Approval IDs and decisions are
502+
transport data and are not exposed through generic scalar helpers or fallback
503+
text.
504+
- **`url` / `web-app` actions** and deprecated **`url` / `webApp` / `web_app`**
505+
inputs render the URL text alongside the button label, since the URL is
506+
user-facing.
469507
- **Select options** render as label-only. The underlying option value is not
470508
exposed in fallback text.
471509

@@ -543,7 +581,9 @@ import {
543581
renderMessagePresentationFallbackText,
544582
renderMessagePresentationTableFallbackText,
545583
resolveMessagePresentationActionValue,
584+
resolveMessagePresentationButtonAction,
546585
resolveMessagePresentationControlValue,
586+
resolveMessagePresentationOptionAction,
547587
} from "openclaw/plugin-sdk/interactive-runtime";
548588
```
549589

@@ -558,10 +598,14 @@ Non-deprecated helpers worth knowing:
558598
`--presentation` flag) into `MessagePresentation`.
559599
- `isMessagePresentationInteractiveBlock(block)` narrows a block to the
560600
`buttons` | `select` union.
601+
- `resolveMessagePresentationButtonAction(button)` and
602+
`resolveMessagePresentationOptionAction(option)` return the canonical typed
603+
action while accepting deprecated boundary fields. An explicit `action`
604+
always wins.
561605
- `resolveMessagePresentationActionValue(action)` /
562-
`resolveMessagePresentationControlValue(control)` read the effective
563-
command/callback value off an `action`, falling back to the legacy `value`
564-
field for `resolveMessagePresentationControlValue`.
606+
`resolveMessagePresentationControlValue(control)` read command/callback
607+
scalar values only. A non-scalar canonical action never falls through to a
608+
legacy shadow `value`, so approval IDs and link targets stay typed.
565609
- `renderMessagePresentationChartFallbackText(block)` /
566610
`renderMessagePresentationTableFallbackText(block)` render one structured
567611
data block as deterministic text for channel-specific fallback paths.
@@ -594,6 +638,13 @@ Approval helpers also have presentation-first replacements:
594638
- use `buildExecApprovalPresentation(...)` instead of
595639
`buildExecApprovalInteractiveReply(...)`
596640

641+
Those shipped builders remain command-backed for plugin compatibility. Gateway
642+
and bundled channel code that owns a durable approval kind should use
643+
`buildTypedApprovalPresentation(...)`,
644+
`buildTypedExecApprovalPendingReplyPayload(...)`, or
645+
`buildTypedPluginApprovalPendingReplyPayload(...)` so transports receive an
646+
explicit `approval` action instead of inferring semantics from `/approve` text.
647+
597648
`renderMessagePresentationFallbackText(...)` returns an empty string for
598649
presentation blocks that have no text fallback, such as a divider-only
599650
presentation. Transports that require a non-empty send body can pass

docs/plugins/sdk-channel-plugins.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ Other approval helpers:
311311
- Preserve the delivered approval id kind end-to-end. Native clients should
312312
not guess or rewrite exec vs plugin approval routing from channel-local
313313
state.
314+
- Pass that explicit `approvalKind` to `resolveApprovalOverGateway`. This uses
315+
the canonical `approval.resolve` service and returns the recorded winner when
316+
another surface answers first. The older explicit `resolveMethod` input
317+
remains for command-backed controls; new native actions must not use it or
318+
infer kind from an ID.
314319
- Different approval kinds can intentionally expose different native
315320
surfaces. Current bundled examples: Matrix keeps the same native DM/channel
316321
routing and reaction UX for exec and plugin approvals, while still letting
@@ -329,6 +334,7 @@ For hot channel entrypoints, prefer these narrower subpaths over the broader
329334
- `openclaw/plugin-sdk/approval-client-runtime`
330335
- `openclaw/plugin-sdk/approval-delivery-runtime`
331336
- `openclaw/plugin-sdk/approval-gateway-runtime`
337+
- `openclaw/plugin-sdk/approval-reference-runtime`
332338
- `openclaw/plugin-sdk/approval-handler-adapter-runtime`
333339
- `openclaw/plugin-sdk/approval-handler-runtime`
334340
- `openclaw/plugin-sdk/approval-native-runtime`

docs/plugins/sdk-migration.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,8 @@ SDK.
425425
| `plugin-sdk/approval-auth-runtime` | Approval auth helpers | Approver resolution, same-chat action auth |
426426
| `plugin-sdk/approval-client-runtime` | Approval client helpers | Native exec approval profile/filter helpers |
427427
| `plugin-sdk/approval-delivery-runtime` | Approval delivery helpers | Native approval capability/delivery adapters |
428-
| `plugin-sdk/approval-gateway-runtime` | Approval gateway helpers | Shared approval gateway-resolution helper |
428+
| `plugin-sdk/approval-gateway-runtime` | Approval gateway helpers | Shared approval gateway resolver |
429+
| `plugin-sdk/approval-reference-runtime` | Approval transport references | Deterministic durable-locator helper for transport-limited callbacks |
429430
| `plugin-sdk/approval-handler-adapter-runtime` | Approval adapter helpers | Lightweight native approval adapter loading helpers for hot channel entrypoints |
430431
| `plugin-sdk/approval-handler-runtime` | Approval handler helpers | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
431432
| `plugin-sdk/approval-native-runtime` | Approval target helpers | Native approval target/account binding helpers |

docs/plugins/sdk-subpaths.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,8 @@ usage endpoint failed or returned no usable usage data.
210210
| `plugin-sdk/approval-auth-runtime` | Approver resolution and same-chat action-auth helpers |
211211
| `plugin-sdk/approval-client-runtime` | Native exec approval profile/filter helpers |
212212
| `plugin-sdk/approval-delivery-runtime` | Native approval capability/delivery adapters |
213-
| `plugin-sdk/approval-gateway-runtime` | Shared approval gateway-resolution helper |
213+
| `plugin-sdk/approval-gateway-runtime` | Shared approval gateway resolver |
214+
| `plugin-sdk/approval-reference-runtime` | Deterministic durable-locator helper for transport-limited approval callbacks |
214215
| `plugin-sdk/approval-handler-adapter-runtime` | Lightweight native approval adapter loading helpers for hot channel entrypoints |
215216
| `plugin-sdk/approval-handler-runtime` | Broader approval handler runtime helpers; prefer the narrower adapter/gateway seams when they are enough |
216217
| `plugin-sdk/approval-native-runtime` | Native approval target, account-binding, route-gate, forwarding fallback, and local native exec prompt suppression helpers |

0 commit comments

Comments
 (0)