From d7a173e60edb40367e899ed3ba51e48c2158d35f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 21 Apr 2026 21:17:41 +0100 Subject: [PATCH] feat(plugin-sdk): add presentation and skills runtime contracts --- docs/.generated/plugin-sdk-api-baseline.sha256 | 4 ++-- docs/plugins/architecture.md | 17 ++++++++++------- docs/plugins/sdk-overview.md | 4 ++-- package.json | 4 ++++ scripts/lib/plugin-sdk-entrypoints.json | 1 + src/plugin-sdk/channel-actions.ts | 17 ++++++++++++----- src/plugin-sdk/interactive-runtime.ts | 18 ++++++++++++++++++ src/plugin-sdk/skills-runtime.ts | 7 +++++++ .../contracts/plugin-sdk-subpaths.test.ts | 9 ++++++--- 9 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 src/plugin-sdk/skills-runtime.ts diff --git a/docs/.generated/plugin-sdk-api-baseline.sha256 b/docs/.generated/plugin-sdk-api-baseline.sha256 index ad04d7d6fe7..1bcbad9175d 100644 --- a/docs/.generated/plugin-sdk-api-baseline.sha256 +++ b/docs/.generated/plugin-sdk-api-baseline.sha256 @@ -1,2 +1,2 @@ -f135ddc1802b7f8b2d29bf495fd0ac1f497a89bab8164ca8c7c8f18efc010e6e plugin-sdk-api-baseline.json -a47d06095ec5c3701a94888a11e89700d8a8511db46fa3122fb9407e160707b6 plugin-sdk-api-baseline.jsonl +cfeee4630cb43ffc4d702f207d28d35962c6458aa8fd2b1671c35e0be158bb35 plugin-sdk-api-baseline.json +af4fbf19861c6ec000b41ac5a3ded597700e45bb15f8b1d74bb2d1f550bd09b6 plugin-sdk-api-baseline.jsonl diff --git a/docs/plugins/architecture.md b/docs/plugins/architecture.md index 9a7ec5fb1d5..8f2fc9e743c 100644 --- a/docs/plugins/architecture.md +++ b/docs/plugins/architecture.md @@ -1251,16 +1251,19 @@ Compatibility note: ## Message tool schemas Plugins should own channel-specific `describeMessageTool(...)` schema -contributions. Keep provider-specific fields in the plugin, not in shared core. +contributions for non-message primitives such as reactions, reads, and polls. +Shared send presentation should use the generic `MessagePresentation` contract +instead of provider-native button, component, block, or card fields. -For shared portable schema fragments, reuse the generic helpers exported through -`openclaw/plugin-sdk/channel-actions`: +Send-capable plugins declare what they can render through message capabilities: -- `createMessageToolButtonsSchema()` for button-grid style payloads -- `createMessageToolCardSchema()` for structured card payloads +- `presentation` for semantic presentation blocks (`text`, `context`, `divider`, `buttons`, `select`) +- `delivery-pin` for pinned-delivery requests -If a schema shape only makes sense for one provider, define it in that plugin's -own source instead of promoting it into the shared SDK. +Core decides whether to render the presentation natively or degrade it to text. +Do not expose provider-native UI escape hatches from the generic message tool. +Deprecated SDK helpers for legacy native schemas remain exported for existing +third-party plugins, but new plugins should not use them. ## Channel target resolution diff --git a/docs/plugins/sdk-overview.md b/docs/plugins/sdk-overview.md index 9613d5f9e95..b5991c9f782 100644 --- a/docs/plugins/sdk-overview.md +++ b/docs/plugins/sdk-overview.md @@ -109,13 +109,13 @@ explicitly promotes one as public. | `plugin-sdk/allowlist-config-edit` | Allowlist config edit/read helpers | | `plugin-sdk/group-access` | Shared group-access decision helpers | | `plugin-sdk/direct-dm` | Shared direct-DM auth/guard helpers | - | `plugin-sdk/interactive-runtime` | Interactive reply payload normalization/reduction helpers | + | `plugin-sdk/interactive-runtime` | Semantic message presentation, delivery, and legacy interactive reply helpers | | `plugin-sdk/channel-inbound` | Compatibility barrel for inbound debounce, mention matching, mention-policy helpers, and envelope helpers | | `plugin-sdk/channel-mention-gating` | Narrow mention-policy helpers without the broader inbound runtime surface | | `plugin-sdk/channel-location` | Channel location context and formatting helpers | | `plugin-sdk/channel-logging` | Channel logging helpers for inbound drops and typing/ack failures | | `plugin-sdk/channel-send-result` | Reply result types | - | `plugin-sdk/channel-actions` | `createMessageToolButtonsSchema`, `createMessageToolCardSchema` | + | `plugin-sdk/channel-actions` | Channel message-action helpers, plus deprecated native schema helpers kept for plugin compatibility | | `plugin-sdk/channel-targets` | Target parsing/matching helpers | | `plugin-sdk/channel-contract` | Channel contract types | | `plugin-sdk/channel-feedback` | Feedback/reaction wiring | diff --git a/package.json b/package.json index b3ddd8f6f19..52d37881c5a 100644 --- a/package.json +++ b/package.json @@ -321,6 +321,10 @@ "types": "./dist/plugin-sdk/plugin-runtime.d.ts", "default": "./dist/plugin-sdk/plugin-runtime.js" }, + "./plugin-sdk/skills-runtime": { + "types": "./dist/plugin-sdk/skills-runtime.d.ts", + "default": "./dist/plugin-sdk/skills-runtime.js" + }, "./plugin-sdk/channel-secret-basic-runtime": { "types": "./dist/plugin-sdk/channel-secret-basic-runtime.d.ts", "default": "./dist/plugin-sdk/channel-secret-basic-runtime.js" diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index bc06c53a014..0c2bc6cc47b 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -66,6 +66,7 @@ "simple-completion-runtime", "speech-core", "plugin-runtime", + "skills-runtime", "channel-secret-basic-runtime", "channel-secret-runtime", "channel-secret-tts-runtime", diff --git a/src/plugin-sdk/channel-actions.ts b/src/plugin-sdk/channel-actions.ts index 7f137cb9430..74e8f8a564a 100644 --- a/src/plugin-sdk/channel-actions.ts +++ b/src/plugin-sdk/channel-actions.ts @@ -1,3 +1,7 @@ +import { Type } from "@sinclair/typebox"; +import type { TSchema } from "@sinclair/typebox"; +import { stringEnum as createStringEnum } from "../agents/schema/typebox.js"; + export { createUnionActionGate, listTokenSourcedAccounts, @@ -20,11 +24,11 @@ export { withNormalizedTimestamp } from "../agents/date-time.js"; export { assertMediaNotDataUrl } from "../agents/sandbox-paths.js"; export { resolvePollMaxSelections } from "../polls.js"; export { optionalStringEnum, stringEnum } from "../agents/schema/typebox.js"; -import { Type } from "@sinclair/typebox"; -import type { TSchema } from "@sinclair/typebox"; -import { stringEnum as createStringEnum } from "../agents/schema/typebox.js"; -/** Schema helper for channels that expose button rows on the shared `message` tool. */ +/** + * @deprecated Use semantic `presentation` capabilities instead of exposing + * provider-native button schemas through the shared message tool. + */ export function createMessageToolButtonsSchema(): TSchema { return Type.Optional( Type.Array( @@ -42,7 +46,10 @@ export function createMessageToolButtonsSchema(): TSchema { ); } -/** Schema helper for channels that accept provider-native card payloads. */ +/** + * @deprecated Use semantic `presentation` capabilities instead of exposing + * provider-native card schemas through the shared message tool. + */ export function createMessageToolCardSchema(): TSchema { return Type.Optional( Type.Object( diff --git a/src/plugin-sdk/interactive-runtime.ts b/src/plugin-sdk/interactive-runtime.ts index 2eef796733a..666b7888c56 100644 --- a/src/plugin-sdk/interactive-runtime.ts +++ b/src/plugin-sdk/interactive-runtime.ts @@ -7,11 +7,29 @@ export type { InteractiveReplyOption, InteractiveReplySelectBlock, InteractiveReplyTextBlock, + MessagePresentation, + MessagePresentationBlock, + MessagePresentationButton, + MessagePresentationButtonStyle, + MessagePresentationButtonsBlock, + MessagePresentationContextBlock, + MessagePresentationDividerBlock, + MessagePresentationOption, + MessagePresentationSelectBlock, + MessagePresentationTextBlock, + MessagePresentationTone, + ReplyPayloadDelivery, + ReplyPayloadDeliveryPin, } from "../interactive/payload.js"; export { hasInteractiveReplyBlocks, + hasMessagePresentationBlocks, hasReplyChannelData, hasReplyContent, + interactiveReplyToPresentation, + normalizeMessagePresentation, normalizeInteractiveReply, + presentationToInteractiveReply, + renderMessagePresentationFallbackText, resolveInteractiveTextFallback, } from "../interactive/payload.js"; diff --git a/src/plugin-sdk/skills-runtime.ts b/src/plugin-sdk/skills-runtime.ts new file mode 100644 index 00000000000..aa83554ce92 --- /dev/null +++ b/src/plugin-sdk/skills-runtime.ts @@ -0,0 +1,7 @@ +export { + bumpSkillsSnapshotVersion, + getSkillsSnapshotVersion, + registerSkillsChangeListener, + shouldRefreshSnapshotForVersion, + type SkillsChangeEvent, +} from "../agents/skills/refresh-state.js"; diff --git a/src/plugins/contracts/plugin-sdk-subpaths.test.ts b/src/plugins/contracts/plugin-sdk-subpaths.test.ts index 281eeb904fb..12b1e134559 100644 --- a/src/plugins/contracts/plugin-sdk-subpaths.test.ts +++ b/src/plugins/contracts/plugin-sdk-subpaths.test.ts @@ -501,7 +501,12 @@ describe("plugin-sdk subpath exports", () => { ], }); expectSourceMentions("account-helpers", ["createAccountListHelpers"]); - expectSourceMentions("channel-actions", ["optionalStringEnum", "stringEnum"]); + expectSourceMentions("channel-actions", [ + "optionalStringEnum", + "stringEnum", + "createMessageToolButtonsSchema", + "createMessageToolCardSchema", + ]); expectSourceContract("channel-secret-basic-runtime", { mentions: [ "collectSimpleChannelFieldAssignments", @@ -750,8 +755,6 @@ describe("plugin-sdk subpath exports", () => { "buildChannelKeyCandidates", "buildMessagingTarget", "createDirectTextMediaOutbound", - "createMessageToolButtonsSchema", - "createMessageToolCardSchema", "createScopedAccountReplyToModeResolver", "createStaticReplyToModeResolver", "createTopLevelChannelReplyToModeResolver",