From 3126809cb06a6ec0e743bd3aa3cac5b1f725b227 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 5 Apr 2026 18:18:47 +0100 Subject: [PATCH] refactor: clean bundled channel bootstrap boundaries --- extensions/bluebubbles/api.ts | 2 + extensions/bluebubbles/index.ts | 20 +- extensions/bluebubbles/runtime-api.ts | 1 + extensions/bluebubbles/setup-entry.ts | 13 +- extensions/discord/api.ts | 7 + extensions/discord/index.ts | 24 +- extensions/discord/runtime-api.ts | 1 + extensions/discord/setup-entry.ts | 13 +- extensions/feishu/api.ts | 11 + extensions/feishu/channel-entry.ts | 20 +- extensions/feishu/index.ts | 125 +++++----- extensions/feishu/runtime-api.ts | 1 + extensions/feishu/setup-entry.ts | 11 +- extensions/googlechat/api.ts | 1 + extensions/googlechat/index.ts | 21 +- extensions/googlechat/runtime-api.ts | 1 + extensions/googlechat/setup-entry.ts | 11 +- extensions/imessage/api.ts | 1 + extensions/imessage/index.ts | 20 +- extensions/imessage/runtime-api.ts | 1 + extensions/imessage/setup-entry.ts | 13 +- extensions/irc/api.ts | 2 + extensions/irc/index.ts | 21 +- extensions/irc/setup-entry.ts | 11 +- extensions/line/api.ts | 3 + extensions/line/index.ts | 25 +- extensions/line/runtime-api.ts | 1 + extensions/line/setup-entry.ts | 13 +- extensions/matrix/api.ts | 1 + extensions/matrix/index.ts | 39 ++- extensions/matrix/setup-entry.ts | 11 +- extensions/mattermost/index.ts | 31 ++- extensions/mattermost/runtime-api.ts | 3 + extensions/mattermost/setup-entry.ts | 11 +- extensions/msteams/api.ts | 1 + extensions/msteams/index.ts | 20 +- extensions/msteams/runtime-api.ts | 1 + extensions/msteams/setup-entry.ts | 11 +- extensions/nextcloud-talk/index.ts | 20 +- extensions/nextcloud-talk/runtime-api.ts | 1 + extensions/nextcloud-talk/setup-entry.ts | 11 +- extensions/nostr/api.ts | 4 + extensions/nostr/index.ts | 54 ++++- extensions/nostr/setup-entry.ts | 11 +- extensions/qqbot/api.ts | 5 + extensions/qqbot/index.ts | 122 ++++++++-- extensions/qqbot/setup-entry.ts | 13 +- extensions/signal/api.ts | 2 + extensions/signal/channel-entry.ts | 20 +- extensions/signal/index.ts | 20 +- extensions/signal/runtime-api.ts | 1 + extensions/signal/setup-entry.ts | 13 +- extensions/slack/api.ts | 2 + extensions/slack/channel-entry.ts | 20 +- extensions/slack/index.ts | 31 ++- extensions/slack/runtime-api.ts | 2 + extensions/slack/setup-entry.ts | 13 +- extensions/synology-chat/api.ts | 2 + extensions/synology-chat/index.ts | 20 +- extensions/synology-chat/setup-entry.ts | 11 +- extensions/telegram/api.ts | 2 + extensions/telegram/index.ts | 21 +- extensions/telegram/runtime-api.ts | 1 + extensions/telegram/setup-entry.ts | 13 +- extensions/tlon/api.ts | 2 + extensions/tlon/index.ts | 29 ++- extensions/tlon/setup-entry.ts | 11 +- extensions/twitch/api.ts | 2 + extensions/twitch/index.ts | 21 +- extensions/whatsapp/api.ts | 2 + extensions/whatsapp/index.ts | 20 +- extensions/whatsapp/runtime-api.ts | 1 + extensions/whatsapp/setup-entry.ts | 13 +- extensions/zalo/api.ts | 4 +- extensions/zalo/index.ts | 20 +- extensions/zalo/runtime-api.ts | 2 + extensions/zalo/setup-api.ts | 34 +++ extensions/zalo/setup-entry.ts | 11 +- extensions/zalouser/api.ts | 3 + extensions/zalouser/index.ts | 34 ++- extensions/zalouser/runtime-api.ts | 1 + extensions/zalouser/setup-entry.ts | 13 +- package.json | 4 + scripts/lib/plugin-sdk-entrypoints.json | 1 + .../plugins/bundled.shape-guard.test.ts | 61 ++++- src/channels/plugins/bundled.ts | 101 +++----- src/plugin-sdk/channel-entry-contract.ts | 227 ++++++++++++++++++ src/plugin-sdk/zalo-setup.ts | 4 +- 88 files changed, 1107 insertions(+), 472 deletions(-) create mode 100644 extensions/zalo/setup-api.ts create mode 100644 src/plugin-sdk/channel-entry-contract.ts diff --git a/extensions/bluebubbles/api.ts b/extensions/bluebubbles/api.ts index c5cd3e10a75..5c1d78810d8 100644 --- a/extensions/bluebubbles/api.ts +++ b/extensions/bluebubbles/api.ts @@ -1,3 +1,5 @@ +export { bluebubblesPlugin } from "./src/channel.js"; +export { bluebubblesSetupPlugin } from "./src/channel.setup.js"; export * from "./src/conversation-id.js"; export * from "./src/conversation-bindings.js"; export { collectBlueBubblesStatusIssues } from "./src/status-issues.js"; diff --git a/extensions/bluebubbles/index.ts b/extensions/bluebubbles/index.ts index 1372f2958f9..eb2ac20c3d7 100644 --- a/extensions/bluebubbles/index.ts +++ b/extensions/bluebubbles/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { bluebubblesPlugin } from "./src/channel.js"; -import { setBlueBubblesRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { bluebubblesPlugin } from "./src/channel.js"; -export { setBlueBubblesRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "bluebubbles", name: "BlueBubbles", description: "BlueBubbles channel plugin (macOS app)", - plugin: bluebubblesPlugin, - setRuntime: setBlueBubblesRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "bluebubblesPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setBlueBubblesRuntime", + }, }); diff --git a/extensions/bluebubbles/runtime-api.ts b/extensions/bluebubbles/runtime-api.ts index 24139381e05..65cbb448959 100644 --- a/extensions/bluebubbles/runtime-api.ts +++ b/extensions/bluebubbles/runtime-api.ts @@ -2,3 +2,4 @@ export { resolveBlueBubblesGroupRequireMention, resolveBlueBubblesGroupToolPolicy, } from "./src/group-policy.js"; +export { setBlueBubblesRuntime } from "./src/runtime.js"; diff --git a/extensions/bluebubbles/setup-entry.ts b/extensions/bluebubbles/setup-entry.ts index a5074b91f59..26502734095 100644 --- a/extensions/bluebubbles/setup-entry.ts +++ b/extensions/bluebubbles/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { bluebubblesSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { bluebubblesSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(bluebubblesSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "bluebubblesSetupPlugin", + }, +}); diff --git a/extensions/discord/api.ts b/extensions/discord/api.ts index d31597d1bdc..b2629843845 100644 --- a/extensions/discord/api.ts +++ b/extensions/discord/api.ts @@ -1,3 +1,10 @@ +export { discordPlugin } from "./src/channel.js"; +export { discordSetupPlugin } from "./src/channel.setup.js"; +export { + handleDiscordSubagentDeliveryTarget, + handleDiscordSubagentEnded, + handleDiscordSubagentSpawning, +} from "./src/subagent-hooks.js"; export * from "./src/account-inspect.js"; export * from "./src/accounts.js"; export * from "./src/actions/handle-action.guild-admin.js"; diff --git a/extensions/discord/index.ts b/extensions/discord/index.ts index df4c3d2ed32..a0bf0d5b2c0 100644 --- a/extensions/discord/index.ts +++ b/extensions/discord/index.ts @@ -1,25 +1,27 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { discordPlugin } from "./src/channel.js"; -import { setDiscordRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { discordPlugin } from "./src/channel.js"; -export { setDiscordRuntime } from "./src/runtime.js"; - -type DiscordSubagentHooksModule = typeof import("./src/subagent-hooks.js"); +type DiscordSubagentHooksModule = typeof import("./api.js"); let discordSubagentHooksPromise: Promise | null = null; function loadDiscordSubagentHooksModule() { - discordSubagentHooksPromise ??= import("./src/subagent-hooks.js"); + discordSubagentHooksPromise ??= import("./api.js"); return discordSubagentHooksPromise; } -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "discord", name: "Discord", description: "Discord channel plugin", - plugin: discordPlugin, - setRuntime: setDiscordRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "discordPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setDiscordRuntime", + }, registerFull(api) { api.on("subagent_spawning", async (event) => { const { handleDiscordSubagentSpawning } = await loadDiscordSubagentHooksModule(); diff --git a/extensions/discord/runtime-api.ts b/extensions/discord/runtime-api.ts index cba83e9d821..f8bb04b3266 100644 --- a/extensions/discord/runtime-api.ts +++ b/extensions/discord/runtime-api.ts @@ -17,3 +17,4 @@ export * from "./src/resolve-users.js"; export * from "./src/outbound-session-route.js"; export * from "./src/send.js"; export * from "./src/send.components.js"; +export { setDiscordRuntime } from "./src/runtime.js"; diff --git a/extensions/discord/setup-entry.ts b/extensions/discord/setup-entry.ts index 66e31c46778..138c98531b3 100644 --- a/extensions/discord/setup-entry.ts +++ b/extensions/discord/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { discordSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { discordSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(discordSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "discordSetupPlugin", + }, +}); diff --git a/extensions/feishu/api.ts b/extensions/feishu/api.ts index 55dfd166d1d..dd8f9c24b88 100644 --- a/extensions/feishu/api.ts +++ b/extensions/feishu/api.ts @@ -1,4 +1,15 @@ export { feishuPlugin } from "./src/channel.js"; +export { registerFeishuDocTools } from "./src/docx.js"; +export { registerFeishuChatTools } from "./src/chat.js"; +export { registerFeishuWikiTools } from "./src/wiki.js"; +export { registerFeishuDriveTools } from "./src/drive.js"; +export { registerFeishuPermTools } from "./src/perm.js"; +export { registerFeishuBitableTools } from "./src/bitable.js"; +export { + handleFeishuSubagentDeliveryTarget, + handleFeishuSubagentEnded, + handleFeishuSubagentSpawning, +} from "./src/subagent-hooks.js"; export * from "./src/conversation-id.js"; export * from "./src/setup-core.js"; export * from "./src/setup-surface.js"; diff --git a/extensions/feishu/channel-entry.ts b/extensions/feishu/channel-entry.ts index b72338740ab..549404e18a8 100644 --- a/extensions/feishu/channel-entry.ts +++ b/extensions/feishu/channel-entry.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { feishuPlugin } from "./src/channel.js"; -import { setFeishuRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { feishuPlugin } from "./src/channel.js"; -export { setFeishuRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "feishu", name: "Feishu", description: "Feishu/Lark channel plugin", - plugin: feishuPlugin, - setRuntime: setFeishuRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "feishuPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setFeishuRuntime", + }, }); diff --git a/extensions/feishu/index.ts b/extensions/feishu/index.ts index 9469da45972..2f149dc612d 100644 --- a/extensions/feishu/index.ts +++ b/extensions/feishu/index.ts @@ -1,78 +1,79 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { registerFeishuBitableTools } from "./src/bitable.js"; -import { feishuPlugin } from "./src/channel.js"; -import { registerFeishuChatTools } from "./src/chat.js"; -import { registerFeishuDocTools } from "./src/docx.js"; -import { registerFeishuDriveTools } from "./src/drive.js"; -import { registerFeishuPermTools } from "./src/perm.js"; -import { setFeishuRuntime } from "./src/runtime.js"; -import { registerFeishuWikiTools } from "./src/wiki.js"; +import { + defineBundledChannelEntry, + loadBundledEntryExportSync, +} from "openclaw/plugin-sdk/channel-entry-contract"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; -export { feishuPlugin } from "./src/channel.js"; -export { setFeishuRuntime } from "./src/runtime.js"; -export { - sendMessageFeishu, - sendCardFeishu, - updateCardFeishu, - editMessageFeishu, - getMessageFeishu, -} from "./src/send.js"; -export { - uploadImageFeishu, - uploadFileFeishu, - sendImageFeishu, - sendFileFeishu, - sendMediaFeishu, -} from "./src/media.js"; -export { probeFeishu } from "./src/probe.js"; -export { - addReactionFeishu, - removeReactionFeishu, - listReactionsFeishu, - FeishuEmoji, -} from "./src/reactions.js"; -export { - extractMentionTargets, - extractMessageBody, - isMentionForwardRequest, - formatMentionForText, - formatMentionForCard, - formatMentionAllForText, - formatMentionAllForCard, - buildMentionedMessage, - buildMentionedCardContent, - type MentionTarget, -} from "./src/mention.js"; +type FeishuSubagentHooksModule = typeof import("./api.js"); -type MonitorFeishuProvider = typeof import("./src/monitor.js").monitorFeishuProvider; -type FeishuSubagentHooksModule = typeof import("./src/subagent-hooks.js"); - -let feishuMonitorPromise: Promise | null = null; let feishuSubagentHooksPromise: Promise | null = null; -function loadFeishuMonitorModule() { - feishuMonitorPromise ??= import("./src/monitor.js"); - return feishuMonitorPromise; -} - function loadFeishuSubagentHooksModule() { - feishuSubagentHooksPromise ??= import("./src/subagent-hooks.js"); + feishuSubagentHooksPromise ??= import("./api.js"); return feishuSubagentHooksPromise; } -export async function monitorFeishuProvider( - ...args: Parameters -): ReturnType { - const { monitorFeishuProvider } = await loadFeishuMonitorModule(); - return await monitorFeishuProvider(...args); +function registerFeishuDocTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuDocTools", + }); + register(api); } -export default defineChannelPluginEntry({ +function registerFeishuChatTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuChatTools", + }); + register(api); +} + +function registerFeishuWikiTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuWikiTools", + }); + register(api); +} + +function registerFeishuDriveTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuDriveTools", + }); + register(api); +} + +function registerFeishuPermTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuPermTools", + }); + register(api); +} + +function registerFeishuBitableTools(api: OpenClawPluginApi) { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerFeishuBitableTools", + }); + register(api); +} + +export default defineBundledChannelEntry({ id: "feishu", name: "Feishu", description: "Feishu/Lark channel plugin", - plugin: feishuPlugin, - setRuntime: setFeishuRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "feishuPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setFeishuRuntime", + }, registerFull(api) { api.on("subagent_spawning", async (event, ctx) => { const { handleFeishuSubagentSpawning } = await loadFeishuSubagentHooksModule(); diff --git a/extensions/feishu/runtime-api.ts b/extensions/feishu/runtime-api.ts index 0828b37c8d6..eec0d4797d0 100644 --- a/extensions/feishu/runtime-api.ts +++ b/extensions/feishu/runtime-api.ts @@ -49,3 +49,4 @@ export { readRequestBodyWithLimit, requestBodyErrorToText, } from "openclaw/plugin-sdk/webhook-ingress"; +export { setFeishuRuntime } from "./src/runtime.js"; diff --git a/extensions/feishu/setup-entry.ts b/extensions/feishu/setup-entry.ts index bfdb856e45f..e4da3147d24 100644 --- a/extensions/feishu/setup-entry.ts +++ b/extensions/feishu/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { feishuPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(feishuPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "feishuPlugin", + }, +}); diff --git a/extensions/googlechat/api.ts b/extensions/googlechat/api.ts index 8f7fe4d268b..1ec2405713e 100644 --- a/extensions/googlechat/api.ts +++ b/extensions/googlechat/api.ts @@ -1,2 +1,3 @@ +export { googlechatPlugin } from "./src/channel.js"; export * from "./src/setup-core.js"; export * from "./src/setup-surface.js"; diff --git a/extensions/googlechat/index.ts b/extensions/googlechat/index.ts index 16d84588d7a..f5d18e80ab9 100644 --- a/extensions/googlechat/index.ts +++ b/extensions/googlechat/index.ts @@ -1,15 +1,16 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { googlechatPlugin } from "./src/channel.js"; -import { setGoogleChatRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { googlechatPlugin } from "./src/channel.js"; -export { setGoogleChatRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "googlechat", name: "Google Chat", description: "OpenClaw Google Chat channel plugin", - plugin: googlechatPlugin as ChannelPlugin, - setRuntime: setGoogleChatRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "googlechatPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setGoogleChatRuntime", + }, }); diff --git a/extensions/googlechat/runtime-api.ts b/extensions/googlechat/runtime-api.ts index f2fd8769d99..984be2e15d0 100644 --- a/extensions/googlechat/runtime-api.ts +++ b/extensions/googlechat/runtime-api.ts @@ -60,3 +60,4 @@ export { readJsonWebhookBodyOrReject, type WebhookInFlightLimiter, } from "openclaw/plugin-sdk/webhook-request-guards"; +export { setGoogleChatRuntime } from "./src/runtime.js"; diff --git a/extensions/googlechat/setup-entry.ts b/extensions/googlechat/setup-entry.ts index ad5dd6ee283..b2824ca2fc1 100644 --- a/extensions/googlechat/setup-entry.ts +++ b/extensions/googlechat/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { googlechatPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(googlechatPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "googlechatPlugin", + }, +}); diff --git a/extensions/imessage/api.ts b/extensions/imessage/api.ts index 1eea0fcefa8..38ad105fd19 100644 --- a/extensions/imessage/api.ts +++ b/extensions/imessage/api.ts @@ -1,4 +1,5 @@ export { imessagePlugin } from "./src/channel.js"; +export { imessageSetupPlugin } from "./src/channel.setup.js"; export * from "./src/accounts.js"; export * from "./src/conversation-bindings.js"; export * from "./src/conversation-id.js"; diff --git a/extensions/imessage/index.ts b/extensions/imessage/index.ts index 26bb14c365a..aeb56673f45 100644 --- a/extensions/imessage/index.ts +++ b/extensions/imessage/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { imessagePlugin } from "./src/channel.js"; -import { setIMessageRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { imessagePlugin } from "./src/channel.js"; -export { setIMessageRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "imessage", name: "iMessage", description: "iMessage channel plugin", - plugin: imessagePlugin, - setRuntime: setIMessageRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "imessagePlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setIMessageRuntime", + }, }); diff --git a/extensions/imessage/runtime-api.ts b/extensions/imessage/runtime-api.ts index 28ebb7e576d..7e1dd30dc6f 100644 --- a/extensions/imessage/runtime-api.ts +++ b/extensions/imessage/runtime-api.ts @@ -29,6 +29,7 @@ export type { MonitorIMessageOpts } from "./src/monitor.js"; export { probeIMessage } from "./src/probe.js"; export type { IMessageProbe } from "./src/probe.js"; export { sendMessageIMessage } from "./src/send.js"; +export { setIMessageRuntime } from "./src/runtime.js"; export { chunkTextForOutbound } from "./src/channel-api.js"; export type IMessageAccountConfig = Omit< diff --git a/extensions/imessage/setup-entry.ts b/extensions/imessage/setup-entry.ts index 1fca32f3e29..0852fd76983 100644 --- a/extensions/imessage/setup-entry.ts +++ b/extensions/imessage/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { imessageSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { imessageSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(imessageSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "imessageSetupPlugin", + }, +}); diff --git a/extensions/irc/api.ts b/extensions/irc/api.ts index 4fae8e966ee..f409c7b5d95 100644 --- a/extensions/irc/api.ts +++ b/extensions/irc/api.ts @@ -1,2 +1,4 @@ +export { ircPlugin } from "./src/channel.js"; +export { setIrcRuntime } from "./src/runtime.js"; export * from "./src/accounts.js"; export * from "./src/setup-surface.js"; diff --git a/extensions/irc/index.ts b/extensions/irc/index.ts index 9ee429c310f..2fe1f5e3c3b 100644 --- a/extensions/irc/index.ts +++ b/extensions/irc/index.ts @@ -1,15 +1,16 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { ircPlugin } from "./src/channel.js"; -import { setIrcRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { ircPlugin } from "./src/channel.js"; -export { setIrcRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "irc", name: "IRC", description: "IRC channel plugin", - plugin: ircPlugin as ChannelPlugin, - setRuntime: setIrcRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "ircPlugin", + }, + runtime: { + specifier: "./api.js", + exportName: "setIrcRuntime", + }, }); diff --git a/extensions/irc/setup-entry.ts b/extensions/irc/setup-entry.ts index cb5d208fd0c..2ea75121957 100644 --- a/extensions/irc/setup-entry.ts +++ b/extensions/irc/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { ircPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(ircPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "ircPlugin", + }, +}); diff --git a/extensions/line/api.ts b/extensions/line/api.ts index b42c8175ea0..71f0999cc60 100644 --- a/extensions/line/api.ts +++ b/extensions/line/api.ts @@ -40,5 +40,8 @@ export { setSetupChannelEnabled, splitSetupEntries, } from "./runtime-api.js"; +export { linePlugin } from "./src/channel.js"; +export { lineSetupPlugin } from "./src/channel.setup.js"; +export { registerLineCardCommand } from "./src/card-command.js"; export * from "./runtime-api.js"; export * from "./setup-api.js"; diff --git a/extensions/line/index.ts b/extensions/line/index.ts index 75a4e2cea35..a9b37c999aa 100644 --- a/extensions/line/index.ts +++ b/extensions/line/index.ts @@ -1,9 +1,7 @@ -import { defineChannelPluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/channel-core"; -import { linePlugin } from "./src/channel.js"; -import { setLineRuntime } from "./src/runtime.js"; - -export { linePlugin } from "./src/channel.js"; -export { setLineRuntime } from "./src/runtime.js"; +import { + defineBundledChannelEntry, + type OpenClawPluginApi, +} from "openclaw/plugin-sdk/channel-entry-contract"; type RegisteredLineCardCommand = Parameters[0]; @@ -12,7 +10,7 @@ let lineCardCommandPromise: Promise | null = null; async function loadLineCardCommand(api: OpenClawPluginApi): Promise { lineCardCommandPromise ??= (async () => { let registered: RegisteredLineCardCommand | null = null; - const { registerLineCardCommand } = await import("./src/card-command.js"); + const { registerLineCardCommand } = await import("./api.js"); registerLineCardCommand({ ...api, registerCommand(command: RegisteredLineCardCommand) { @@ -27,12 +25,19 @@ async function loadLineCardCommand(api: OpenClawPluginApi): Promise void>(import.meta.url, { + specifier: "./cli-metadata.js", + exportName: "registerMatrixCliMetadata", + }); + register(api); +} -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "matrix", name: "Matrix", description: "Matrix channel plugin (matrix-js-sdk)", - plugin: matrixPlugin, - setRuntime: setMatrixRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "matrixPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setMatrixRuntime", + }, registerCliMetadata: registerMatrixCliMetadata, registerFull(api) { - void import("./src/plugin-entry.runtime.js") + void import("./plugin-entry.handlers.runtime.js") .then(({ ensureMatrixCryptoRuntime }) => ensureMatrixCryptoRuntime({ log: api.logger.info }).catch((err: unknown) => { const message = err instanceof Error ? err.message : String(err); @@ -27,17 +40,17 @@ export default defineChannelPluginEntry({ }); api.registerGatewayMethod("matrix.verify.recoveryKey", async (ctx) => { - const { handleVerifyRecoveryKey } = await import("./src/plugin-entry.runtime.js"); + const { handleVerifyRecoveryKey } = await import("./plugin-entry.handlers.runtime.js"); await handleVerifyRecoveryKey(ctx); }); api.registerGatewayMethod("matrix.verify.bootstrap", async (ctx) => { - const { handleVerificationBootstrap } = await import("./src/plugin-entry.runtime.js"); + const { handleVerificationBootstrap } = await import("./plugin-entry.handlers.runtime.js"); await handleVerificationBootstrap(ctx); }); api.registerGatewayMethod("matrix.verify.status", async (ctx) => { - const { handleVerificationStatus } = await import("./src/plugin-entry.runtime.js"); + const { handleVerificationStatus } = await import("./plugin-entry.handlers.runtime.js"); await handleVerificationStatus(ctx); }); }, diff --git a/extensions/matrix/setup-entry.ts b/extensions/matrix/setup-entry.ts index c1a6c63e9b4..d23aabec32c 100644 --- a/extensions/matrix/setup-entry.ts +++ b/extensions/matrix/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { matrixPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(matrixPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "matrixPlugin", + }, +}); diff --git a/extensions/mattermost/index.ts b/extensions/mattermost/index.ts index a6b8999734f..893987e9ccc 100644 --- a/extensions/mattermost/index.ts +++ b/extensions/mattermost/index.ts @@ -1,17 +1,30 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { mattermostPlugin } from "./src/channel.js"; -import { registerSlashCommandRoute } from "./src/mattermost/slash-state.js"; -import { setMattermostRuntime } from "./src/runtime.js"; +import { + defineBundledChannelEntry, + loadBundledEntryExportSync, +} from "openclaw/plugin-sdk/channel-entry-contract"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; -export { mattermostPlugin } from "./src/channel.js"; -export { setMattermostRuntime } from "./src/runtime.js"; +function registerSlashCommandRoute(api: OpenClawPluginApi): void { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./runtime-api.js", + exportName: "registerSlashCommandRoute", + }); + register(api); +} -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "mattermost", name: "Mattermost", description: "Mattermost channel plugin", - plugin: mattermostPlugin, - setRuntime: setMattermostRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./runtime-api.js", + exportName: "mattermostPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setMattermostRuntime", + }, registerFull(api) { // Actual slash-command registration happens after the monitor connects and // knows the team id; the route itself can be wired here. diff --git a/extensions/mattermost/runtime-api.ts b/extensions/mattermost/runtime-api.ts index e77586cba1a..fbec359bbc1 100644 --- a/extensions/mattermost/runtime-api.ts +++ b/extensions/mattermost/runtime-api.ts @@ -57,6 +57,7 @@ export { resolveDmGroupAccessWithLists, resolveEffectiveAllowFromLists, } from "openclaw/plugin-sdk/channel-policy"; +export { mattermostPlugin } from "./src/channel.js"; export { evaluateSenderGroupAccessForPolicy } from "openclaw/plugin-sdk/group-access"; export { createChannelReplyPipeline } from "openclaw/plugin-sdk/channel-reply-pipeline"; export { logTypingFailure } from "openclaw/plugin-sdk/channel-feedback"; @@ -86,3 +87,5 @@ export { resolveChannelMediaMaxBytes, } from "openclaw/plugin-sdk/media-runtime"; export { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared"; +export { registerSlashCommandRoute } from "./src/mattermost/slash-state.js"; +export { setMattermostRuntime } from "./src/runtime.js"; diff --git a/extensions/mattermost/setup-entry.ts b/extensions/mattermost/setup-entry.ts index 2564793aa2c..1a68022754d 100644 --- a/extensions/mattermost/setup-entry.ts +++ b/extensions/mattermost/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { mattermostPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(mattermostPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./runtime-api.js", + exportName: "mattermostPlugin", + }, +}); diff --git a/extensions/msteams/api.ts b/extensions/msteams/api.ts index 8f7fe4d268b..5713c49c661 100644 --- a/extensions/msteams/api.ts +++ b/extensions/msteams/api.ts @@ -1,2 +1,3 @@ +export { msteamsPlugin } from "./src/channel.js"; export * from "./src/setup-core.js"; export * from "./src/setup-surface.js"; diff --git a/extensions/msteams/index.ts b/extensions/msteams/index.ts index 86214a6e800..38101a3f281 100644 --- a/extensions/msteams/index.ts +++ b/extensions/msteams/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { msteamsPlugin } from "./src/channel.js"; -import { setMSTeamsRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { msteamsPlugin } from "./src/channel.js"; -export { setMSTeamsRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "msteams", name: "Microsoft Teams", description: "Microsoft Teams channel plugin (Bot Framework)", - plugin: msteamsPlugin, - setRuntime: setMSTeamsRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "msteamsPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setMSTeamsRuntime", + }, }); diff --git a/extensions/msteams/runtime-api.ts b/extensions/msteams/runtime-api.ts index d32cb7b65d5..bb71ffb851e 100644 --- a/extensions/msteams/runtime-api.ts +++ b/extensions/msteams/runtime-api.ts @@ -2,3 +2,4 @@ // Keep this barrel thin and aligned with the local extension surface. export * from "openclaw/plugin-sdk/msteams"; +export { setMSTeamsRuntime } from "./src/runtime.js"; diff --git a/extensions/msteams/setup-entry.ts b/extensions/msteams/setup-entry.ts index 4a08bd31dd3..a9c5cb040f4 100644 --- a/extensions/msteams/setup-entry.ts +++ b/extensions/msteams/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { msteamsPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(msteamsPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "msteamsPlugin", + }, +}); diff --git a/extensions/nextcloud-talk/index.ts b/extensions/nextcloud-talk/index.ts index d5aebc4478f..cb26a275c40 100644 --- a/extensions/nextcloud-talk/index.ts +++ b/extensions/nextcloud-talk/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { nextcloudTalkPlugin } from "./src/channel.js"; -import { setNextcloudTalkRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { nextcloudTalkPlugin } from "./src/channel.js"; -export { setNextcloudTalkRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "nextcloud-talk", name: "Nextcloud Talk", description: "Nextcloud Talk channel plugin", - plugin: nextcloudTalkPlugin, - setRuntime: setNextcloudTalkRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "nextcloudTalkPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setNextcloudTalkRuntime", + }, }); diff --git a/extensions/nextcloud-talk/runtime-api.ts b/extensions/nextcloud-talk/runtime-api.ts index b2093a7a057..de22ca71abb 100644 --- a/extensions/nextcloud-talk/runtime-api.ts +++ b/extensions/nextcloud-talk/runtime-api.ts @@ -2,3 +2,4 @@ // Keep this barrel thin and aligned with the local extension surface. export * from "openclaw/plugin-sdk/nextcloud-talk"; +export { setNextcloudTalkRuntime } from "./src/runtime.js"; diff --git a/extensions/nextcloud-talk/setup-entry.ts b/extensions/nextcloud-talk/setup-entry.ts index 6fbfbd20e5d..67206bc9056 100644 --- a/extensions/nextcloud-talk/setup-entry.ts +++ b/extensions/nextcloud-talk/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { nextcloudTalkPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(nextcloudTalkPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "nextcloudTalkPlugin", + }, +}); diff --git a/extensions/nostr/api.ts b/extensions/nostr/api.ts index 6606fb316b4..20c8d6b7922 100644 --- a/extensions/nostr/api.ts +++ b/extensions/nostr/api.ts @@ -1 +1,5 @@ export * from "./runtime-api.js"; +export { nostrPlugin } from "./src/channel.js"; +export { createNostrProfileHttpHandler } from "./src/nostr-profile-http.js"; +export { getNostrRuntime, setNostrRuntime } from "./src/runtime.js"; +export { resolveNostrAccount } from "./src/types.js"; diff --git a/extensions/nostr/index.ts b/extensions/nostr/index.ts index 07ee38d4b43..639ce36ee5c 100644 --- a/extensions/nostr/index.ts +++ b/extensions/nostr/index.ts @@ -1,28 +1,56 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { nostrPlugin } from "./src/channel.js"; -import type { NostrProfile } from "./src/config-schema.js"; -import { createNostrProfileHttpHandler } from "./src/nostr-profile-http.js"; -import { getNostrRuntime, setNostrRuntime } from "./src/runtime.js"; -import { resolveNostrAccount } from "./src/types.js"; +import { + defineBundledChannelEntry, + loadBundledEntryExportSync, +} from "openclaw/plugin-sdk/channel-entry-contract"; -export { nostrPlugin } from "./src/channel.js"; -export { setNostrRuntime } from "./src/runtime.js"; +function createNostrProfileHttpHandler() { + return loadBundledEntryExportSync< + (params: Record) => (ctx: unknown) => Promise | void + >(import.meta.url, { + specifier: "./api.js", + exportName: "createNostrProfileHttpHandler", + }); +} -export default defineChannelPluginEntry({ +function getNostrRuntime() { + return loadBundledEntryExportSync<() => any>(import.meta.url, { + specifier: "./api.js", + exportName: "getNostrRuntime", + })(); +} + +function resolveNostrAccount(params: { cfg: unknown; accountId: string }) { + return loadBundledEntryExportSync<(params: { cfg: unknown; accountId: string }) => any>( + import.meta.url, + { + specifier: "./api.js", + exportName: "resolveNostrAccount", + }, + )(params); +} + +export default defineBundledChannelEntry({ id: "nostr", name: "Nostr", description: "Nostr DM channel plugin via NIP-04", - plugin: nostrPlugin, - setRuntime: setNostrRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "nostrPlugin", + }, + runtime: { + specifier: "./api.js", + exportName: "setNostrRuntime", + }, registerFull(api) { - const httpHandler = createNostrProfileHttpHandler({ + const httpHandler = createNostrProfileHttpHandler()({ getConfigProfile: (accountId: string) => { const runtime = getNostrRuntime(); const cfg = runtime.config.loadConfig(); const account = resolveNostrAccount({ cfg, accountId }); return account.profile; }, - updateConfigProfile: async (accountId: string, profile: NostrProfile) => { + updateConfigProfile: async (accountId: string, profile: unknown) => { const runtime = getNostrRuntime(); const cfg = runtime.config.loadConfig(); diff --git a/extensions/nostr/setup-entry.ts b/extensions/nostr/setup-entry.ts index 769f4c364f2..6dffaed9bcb 100644 --- a/extensions/nostr/setup-entry.ts +++ b/extensions/nostr/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { nostrPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(nostrPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "nostrPlugin", + }, +}); diff --git a/extensions/qqbot/api.ts b/extensions/qqbot/api.ts index 291b2d5941b..6d412084628 100644 --- a/extensions/qqbot/api.ts +++ b/extensions/qqbot/api.ts @@ -1,3 +1,8 @@ +export { qqbotPlugin } from "./src/channel.js"; +export { qqbotSetupPlugin } from "./src/channel.setup.js"; +export { getFrameworkCommands } from "./src/slash-commands.js"; +export { registerChannelTool } from "./src/tools/channel.js"; +export { registerRemindTool } from "./src/tools/remind.js"; export * from "./src/types.js"; export * from "./src/config.js"; export * from "./src/outbound.js"; diff --git a/extensions/qqbot/index.ts b/extensions/qqbot/index.ts index cac2e1e5c7a..65326d8956a 100644 --- a/extensions/qqbot/index.ts +++ b/extensions/qqbot/index.ts @@ -1,26 +1,96 @@ -import type { - ChannelPlugin, - OpenClawPluginApi, - PluginCommandContext, -} from "openclaw/plugin-sdk/channel-core"; -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { qqbotPlugin } from "./src/channel.js"; -import { resolveQQBotAccount } from "./src/config.js"; -import { sendDocument, type MediaTargetContext } from "./src/outbound.js"; -import { setQQBotRuntime } from "./src/runtime.js"; -import { getFrameworkCommands } from "./src/slash-commands.js"; -import { registerChannelTool } from "./src/tools/channel.js"; -import { registerRemindTool } from "./src/tools/remind.js"; +import { + defineBundledChannelEntry, + loadBundledEntryExportSync, + type OpenClawPluginApi, + type PluginCommandContext, +} from "openclaw/plugin-sdk/channel-entry-contract"; -export { qqbotPlugin } from "./src/channel.js"; -export { setQQBotRuntime, getQQBotRuntime } from "./src/runtime.js"; +type QQBotAccount = { + accountId: string; + appId: string; + config: unknown; +}; -export default defineChannelPluginEntry({ +type MediaTargetContext = { + targetType: "c2c" | "group" | "channel" | "dm"; + targetId: string; + account: QQBotAccount; + logPrefix: string; +}; + +type QQBotFrameworkCommandResult = + | string + | { + text: string; + filePath?: string; + } + | null + | undefined; + +type QQBotFrameworkCommand = { + name: string; + description: string; + handler: (ctx: Record) => Promise; +}; + +function resolveQQBotAccount(config: unknown, accountId?: string): QQBotAccount { + const resolve = loadBundledEntryExportSync<(config: unknown, accountId?: string) => QQBotAccount>( + import.meta.url, + { + specifier: "./api.js", + exportName: "resolveQQBotAccount", + }, + ); + return resolve(config, accountId); +} + +function sendDocument(context: MediaTargetContext, filePath: string) { + const send = loadBundledEntryExportSync< + (context: MediaTargetContext, filePath: string) => Promise + >(import.meta.url, { + specifier: "./api.js", + exportName: "sendDocument", + }); + return send(context, filePath); +} + +function getFrameworkCommands(): QQBotFrameworkCommand[] { + const getCommands = loadBundledEntryExportSync<() => QQBotFrameworkCommand[]>(import.meta.url, { + specifier: "./api.js", + exportName: "getFrameworkCommands", + }); + return getCommands(); +} + +function registerChannelTool(api: OpenClawPluginApi): void { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerChannelTool", + }); + register(api); +} + +function registerRemindTool(api: OpenClawPluginApi): void { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./api.js", + exportName: "registerRemindTool", + }); + register(api); +} + +export default defineBundledChannelEntry({ id: "qqbot", name: "QQ Bot", description: "QQ Bot channel plugin", - plugin: qqbotPlugin as ChannelPlugin, - setRuntime: setQQBotRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "qqbotPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setQQBotRuntime", + }, registerFull(api: OpenClawPluginApi) { registerChannelTool(api); registerRemindTool(api); @@ -95,7 +165,7 @@ export default defineChannelPluginEntry({ } // File result: send the file attachment via QQ API, return text summary. - if (result && "filePath" in result) { + if (result && typeof result === "object" && "filePath" in result) { try { const mediaCtx: MediaTargetContext = { targetType, @@ -103,14 +173,22 @@ export default defineChannelPluginEntry({ account, logPrefix: `[qqbot:${account.accountId}]`, }; - await sendDocument(mediaCtx, result.filePath); + await sendDocument(mediaCtx, String(result.filePath)); } catch { // File send failed; the text summary is still returned below. } - return { text: result.text }; + return { text: String(result.text) }; } - return { text: "⚠️ 命令返回了意外结果。" }; + return { + text: + result && + typeof result === "object" && + "text" in result && + typeof result.text === "string" + ? result.text + : "⚠️ 命令返回了意外结果。", + }; }, }); } diff --git a/extensions/qqbot/setup-entry.ts b/extensions/qqbot/setup-entry.ts index 0786409e78d..d9fb6ab2c07 100644 --- a/extensions/qqbot/setup-entry.ts +++ b/extensions/qqbot/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { qqbotSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { qqbotSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(qqbotSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "qqbotSetupPlugin", + }, +}); diff --git a/extensions/signal/api.ts b/extensions/signal/api.ts index a1963c95fd0..7a39601974f 100644 --- a/extensions/signal/api.ts +++ b/extensions/signal/api.ts @@ -1,3 +1,5 @@ +export { signalPlugin } from "./src/channel.js"; +export { signalSetupPlugin } from "./src/channel.setup.js"; export * from "./src/accounts.js"; export * from "./src/format.js"; export * from "./src/identity.js"; diff --git a/extensions/signal/channel-entry.ts b/extensions/signal/channel-entry.ts index b31a0f0980c..38abe83a5bb 100644 --- a/extensions/signal/channel-entry.ts +++ b/extensions/signal/channel-entry.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { signalPlugin } from "./src/channel.js"; -import { setSignalRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { signalPlugin } from "./src/channel.js"; -export { setSignalRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "signal", name: "Signal", description: "Signal channel plugin", - plugin: signalPlugin, - setRuntime: setSignalRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "signalPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setSignalRuntime", + }, }); diff --git a/extensions/signal/index.ts b/extensions/signal/index.ts index b31a0f0980c..38abe83a5bb 100644 --- a/extensions/signal/index.ts +++ b/extensions/signal/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { signalPlugin } from "./src/channel.js"; -import { setSignalRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { signalPlugin } from "./src/channel.js"; -export { setSignalRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "signal", name: "Signal", description: "Signal channel plugin", - plugin: signalPlugin, - setRuntime: setSignalRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "signalPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setSignalRuntime", + }, }); diff --git a/extensions/signal/runtime-api.ts b/extensions/signal/runtime-api.ts index 801051438fb..e0552014446 100644 --- a/extensions/signal/runtime-api.ts +++ b/extensions/signal/runtime-api.ts @@ -1 +1,2 @@ export * from "./src/runtime-api.js"; +export { setSignalRuntime } from "./src/runtime.js"; diff --git a/extensions/signal/setup-entry.ts b/extensions/signal/setup-entry.ts index ef1016b454a..b38447c39b6 100644 --- a/extensions/signal/setup-entry.ts +++ b/extensions/signal/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { signalSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { signalSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(signalSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "signalSetupPlugin", + }, +}); diff --git a/extensions/slack/api.ts b/extensions/slack/api.ts index 0a80058aed8..0f729d38740 100644 --- a/extensions/slack/api.ts +++ b/extensions/slack/api.ts @@ -1,3 +1,5 @@ +export { slackPlugin } from "./src/channel.js"; +export { slackSetupPlugin } from "./src/channel.setup.js"; export * from "./src/account-inspect.js"; export * from "./src/accounts.js"; export * from "./src/action-threading.js"; diff --git a/extensions/slack/channel-entry.ts b/extensions/slack/channel-entry.ts index 4e5a97fefd4..b23f1672e49 100644 --- a/extensions/slack/channel-entry.ts +++ b/extensions/slack/channel-entry.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { slackPlugin } from "./src/channel.js"; -import { setSlackRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { slackPlugin } from "./src/channel.js"; -export { setSlackRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "slack", name: "Slack", description: "Slack channel plugin", - plugin: slackPlugin, - setRuntime: setSlackRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "slackPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setSlackRuntime", + }, }); diff --git a/extensions/slack/index.ts b/extensions/slack/index.ts index 8d94d42b2f6..260b084b6f5 100644 --- a/extensions/slack/index.ts +++ b/extensions/slack/index.ts @@ -1,16 +1,29 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { slackPlugin } from "./src/channel.js"; -import { registerSlackPluginHttpRoutes } from "./src/http/plugin-routes.js"; -import { setSlackRuntime } from "./src/runtime.js"; +import { + defineBundledChannelEntry, + loadBundledEntryExportSync, +} from "openclaw/plugin-sdk/channel-entry-contract"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; -export { slackPlugin } from "./src/channel.js"; -export { setSlackRuntime } from "./src/runtime.js"; +function registerSlackPluginHttpRoutes(api: OpenClawPluginApi): void { + const register = loadBundledEntryExportSync<(api: OpenClawPluginApi) => void>(import.meta.url, { + specifier: "./runtime-api.js", + exportName: "registerSlackPluginHttpRoutes", + }); + register(api); +} -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "slack", name: "Slack", description: "Slack channel plugin", - plugin: slackPlugin, - setRuntime: setSlackRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "slackPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setSlackRuntime", + }, registerFull: registerSlackPluginHttpRoutes, }); diff --git a/extensions/slack/runtime-api.ts b/extensions/slack/runtime-api.ts index 68281fd83d3..a2173294c20 100644 --- a/extensions/slack/runtime-api.ts +++ b/extensions/slack/runtime-api.ts @@ -3,3 +3,5 @@ export * from "./src/directory-live.js"; export * from "./src/index.js"; export * from "./src/resolve-channels.js"; export * from "./src/resolve-users.js"; +export { registerSlackPluginHttpRoutes } from "./src/http/plugin-routes.js"; +export { setSlackRuntime } from "./src/runtime.js"; diff --git a/extensions/slack/setup-entry.ts b/extensions/slack/setup-entry.ts index 146157e103f..3925b051760 100644 --- a/extensions/slack/setup-entry.ts +++ b/extensions/slack/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { slackSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { slackSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(slackSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "slackSetupPlugin", + }, +}); diff --git a/extensions/synology-chat/api.ts b/extensions/synology-chat/api.ts index ff9159142ab..c7959d560fc 100644 --- a/extensions/synology-chat/api.ts +++ b/extensions/synology-chat/api.ts @@ -1 +1,3 @@ +export { synologyChatPlugin } from "./src/channel.js"; +export { setSynologyRuntime } from "./src/runtime.js"; export * from "./src/security-audit.js"; diff --git a/extensions/synology-chat/index.ts b/extensions/synology-chat/index.ts index d48e2abfa84..909d4fe22e9 100644 --- a/extensions/synology-chat/index.ts +++ b/extensions/synology-chat/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { synologyChatPlugin } from "./src/channel.js"; -import { setSynologyRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { synologyChatPlugin } from "./src/channel.js"; -export { setSynologyRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "synology-chat", name: "Synology Chat", description: "Native Synology Chat channel plugin for OpenClaw", - plugin: synologyChatPlugin, - setRuntime: setSynologyRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "synologyChatPlugin", + }, + runtime: { + specifier: "./api.js", + exportName: "setSynologyRuntime", + }, }); diff --git a/extensions/synology-chat/setup-entry.ts b/extensions/synology-chat/setup-entry.ts index 9d3610a3650..ba20bf1f3d3 100644 --- a/extensions/synology-chat/setup-entry.ts +++ b/extensions/synology-chat/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { synologyChatPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(synologyChatPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "synologyChatPlugin", + }, +}); diff --git a/extensions/telegram/api.ts b/extensions/telegram/api.ts index 06dffa6169c..45d739b080d 100644 --- a/extensions/telegram/api.ts +++ b/extensions/telegram/api.ts @@ -1,3 +1,5 @@ +export { telegramPlugin } from "./src/channel.js"; +export { telegramSetupPlugin } from "./src/channel.setup.js"; export * from "./src/account-inspect.js"; export * from "./src/accounts.js"; export * from "./src/action-threading.js"; diff --git a/extensions/telegram/index.ts b/extensions/telegram/index.ts index cdbc1d3a8e1..0464c2e8fab 100644 --- a/extensions/telegram/index.ts +++ b/extensions/telegram/index.ts @@ -1,15 +1,16 @@ -import type { ChannelPlugin } from "openclaw/plugin-sdk/channel-core"; -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { telegramPlugin } from "./src/channel.js"; -import { setTelegramRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { telegramPlugin } from "./src/channel.js"; -export { setTelegramRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "telegram", name: "Telegram", description: "Telegram channel plugin", - plugin: telegramPlugin as ChannelPlugin, - setRuntime: setTelegramRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "telegramPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setTelegramRuntime", + }, }); diff --git a/extensions/telegram/runtime-api.ts b/extensions/telegram/runtime-api.ts index af68caf0215..815b8e1390a 100644 --- a/extensions/telegram/runtime-api.ts +++ b/extensions/telegram/runtime-api.ts @@ -83,6 +83,7 @@ export { setTelegramThreadBindingMaxAgeBySessionKey, } from "./src/thread-bindings.js"; export { resolveTelegramToken } from "./src/token.js"; +export { setTelegramRuntime } from "./src/runtime.js"; export type { ChannelPlugin } from "openclaw/plugin-sdk/core"; export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; export type TelegramAccountConfig = NonNullable< diff --git a/extensions/telegram/setup-entry.ts b/extensions/telegram/setup-entry.ts index eb9e5ec4339..337c10e57f7 100644 --- a/extensions/telegram/setup-entry.ts +++ b/extensions/telegram/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { telegramSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { telegramSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(telegramSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "telegramSetupPlugin", + }, +}); diff --git a/extensions/tlon/api.ts b/extensions/tlon/api.ts index 6606fb316b4..daa7fcf4ae3 100644 --- a/extensions/tlon/api.ts +++ b/extensions/tlon/api.ts @@ -1 +1,3 @@ export * from "./runtime-api.js"; +export { tlonPlugin } from "./src/channel.js"; +export { setTlonRuntime } from "./src/runtime.js"; diff --git a/extensions/tlon/index.ts b/extensions/tlon/index.ts index 6f8c224face..06ebc2aa47a 100644 --- a/extensions/tlon/index.ts +++ b/extensions/tlon/index.ts @@ -2,12 +2,7 @@ import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { tlonPlugin } from "./src/channel.js"; -import { setTlonRuntime } from "./src/runtime.js"; - -export { tlonPlugin } from "./src/channel.js"; -export { setTlonRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -117,12 +112,19 @@ function runTlonCommand(binary: string, args: string[]): Promise { }); } -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "tlon", name: "Tlon", description: "Tlon/Urbit channel plugin", - plugin: tlonPlugin, - setRuntime: setTlonRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "tlonPlugin", + }, + runtime: { + specifier: "./api.js", + exportName: "setTlonRuntime", + }, registerFull(api) { api.logger.debug?.("[tlon] Registering tlon tool"); api.registerTool({ @@ -164,9 +166,14 @@ export default defineChannelPluginEntry({ content: [{ type: "text" as const, text: output }], details: undefined, }; - } catch (error: any) { + } catch (error: unknown) { return { - content: [{ type: "text" as const, text: `Error: ${error.message}` }], + content: [ + { + type: "text" as const, + text: `Error: ${error instanceof Error ? error.message : String(error)}`, + }, + ], details: { error: true }, }; } diff --git a/extensions/tlon/setup-entry.ts b/extensions/tlon/setup-entry.ts index de25eaa266e..f66fbe818e3 100644 --- a/extensions/tlon/setup-entry.ts +++ b/extensions/tlon/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { tlonPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(tlonPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "tlonPlugin", + }, +}); diff --git a/extensions/twitch/api.ts b/extensions/twitch/api.ts index 6606fb316b4..258dce611ae 100644 --- a/extensions/twitch/api.ts +++ b/extensions/twitch/api.ts @@ -1 +1,3 @@ export * from "./runtime-api.js"; +export { twitchPlugin } from "./src/plugin.js"; +export { setTwitchRuntime } from "./src/runtime.js"; diff --git a/extensions/twitch/index.ts b/extensions/twitch/index.ts index 3c89063034e..c9d078a5424 100644 --- a/extensions/twitch/index.ts +++ b/extensions/twitch/index.ts @@ -1,13 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { twitchPlugin } from "./src/plugin.js"; -import { setTwitchRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { monitorTwitchProvider } from "./src/monitor.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "twitch", name: "Twitch", - description: "Twitch chat channel plugin", - plugin: twitchPlugin, - setRuntime: setTwitchRuntime, + description: "Twitch IRC chat channel plugin", + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "twitchPlugin", + }, + runtime: { + specifier: "./api.js", + exportName: "setTwitchRuntime", + }, }); diff --git a/extensions/whatsapp/api.ts b/extensions/whatsapp/api.ts index f5661c1d8dc..97575ed0a13 100644 --- a/extensions/whatsapp/api.ts +++ b/extensions/whatsapp/api.ts @@ -1,3 +1,5 @@ +export { whatsappPlugin } from "./src/channel.js"; +export { whatsappSetupPlugin } from "./src/channel.setup.js"; export * from "./src/accounts.js"; export * from "./src/auto-reply/constants.js"; export { whatsappCommandPolicy } from "./src/command-policy.js"; diff --git a/extensions/whatsapp/index.ts b/extensions/whatsapp/index.ts index ecf77d6f544..360861c94c8 100644 --- a/extensions/whatsapp/index.ts +++ b/extensions/whatsapp/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { whatsappPlugin } from "./src/channel.js"; -import { setWhatsAppRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { whatsappPlugin } from "./src/channel.js"; -export { setWhatsAppRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "whatsapp", name: "WhatsApp", description: "WhatsApp channel plugin", - plugin: whatsappPlugin, - setRuntime: setWhatsAppRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "whatsappPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setWhatsAppRuntime", + }, }); diff --git a/extensions/whatsapp/runtime-api.ts b/extensions/whatsapp/runtime-api.ts index f3bd788d9f9..eaefbbcfdb8 100644 --- a/extensions/whatsapp/runtime-api.ts +++ b/extensions/whatsapp/runtime-api.ts @@ -8,6 +8,7 @@ export * from "./src/login.js"; export * from "./src/media.js"; export * from "./src/send.js"; export * from "./src/session.js"; +export { setWhatsAppRuntime } from "./src/runtime.js"; type StartWebLoginWithQr = typeof import("./src/login-qr.js").startWebLoginWithQr; type WaitForWebLogin = typeof import("./src/login-qr.js").waitForWebLogin; diff --git a/extensions/whatsapp/setup-entry.ts b/extensions/whatsapp/setup-entry.ts index 9fab8788ecb..b6bfcff52ed 100644 --- a/extensions/whatsapp/setup-entry.ts +++ b/extensions/whatsapp/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { whatsappSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { whatsappSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(whatsappSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "whatsappSetupPlugin", + }, +}); diff --git a/extensions/zalo/api.ts b/extensions/zalo/api.ts index a0a79fc5216..53bd5a5553f 100644 --- a/extensions/zalo/api.ts +++ b/extensions/zalo/api.ts @@ -1,3 +1 @@ -export * from "./src/setup-core.js"; -export * from "./src/setup-surface.js"; -export { evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy } from "./src/group-access.js"; +export * from "./setup-api.js"; diff --git a/extensions/zalo/index.ts b/extensions/zalo/index.ts index 13fdb4174da..7deac82d585 100644 --- a/extensions/zalo/index.ts +++ b/extensions/zalo/index.ts @@ -1,14 +1,16 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { zaloPlugin } from "./src/channel.js"; -import { setZaloRuntime } from "./src/runtime.js"; +import { defineBundledChannelEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { zaloPlugin } from "./src/channel.js"; -export { setZaloRuntime } from "./src/runtime.js"; - -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "zalo", name: "Zalo", description: "Zalo channel plugin", - plugin: zaloPlugin, - setRuntime: setZaloRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./runtime-api.js", + exportName: "zaloPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setZaloRuntime", + }, }); diff --git a/extensions/zalo/runtime-api.ts b/extensions/zalo/runtime-api.ts index 22ae993ca5f..9d3ea845a9e 100644 --- a/extensions/zalo/runtime-api.ts +++ b/extensions/zalo/runtime-api.ts @@ -1,6 +1,7 @@ // Private runtime barrel for the bundled Zalo extension. // Keep this barrel thin and aligned with the local extension surface. +export { zaloPlugin } from "./src/channel.js"; export * from "./api.js"; export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; export type { OpenClawConfig, GroupPolicy } from "openclaw/plugin-sdk/config-runtime"; @@ -92,3 +93,4 @@ export type { RegisterWebhookPluginRouteOptions, RegisterWebhookTargetOptions, } from "openclaw/plugin-sdk/webhook-ingress"; +export { setZaloRuntime } from "./src/runtime.js"; diff --git a/extensions/zalo/setup-api.ts b/extensions/zalo/setup-api.ts new file mode 100644 index 00000000000..4291676bb6d --- /dev/null +++ b/extensions/zalo/setup-api.ts @@ -0,0 +1,34 @@ +import { loadBundledEntryExportSync } from "openclaw/plugin-sdk/channel-entry-contract"; + +type SetupSurfaceModule = typeof import("./src/setup-surface.js"); + +function createLazyObjectValue(load: () => T): T { + return new Proxy({} as T, { + get(_target, property, receiver) { + return Reflect.get(load(), property, receiver); + }, + has(_target, property) { + return property in load(); + }, + ownKeys() { + return Reflect.ownKeys(load()); + }, + getOwnPropertyDescriptor(_target, property) { + const descriptor = Object.getOwnPropertyDescriptor(load(), property); + return descriptor ? { ...descriptor, configurable: true } : undefined; + }, + }); +} + +function loadSetupSurfaceModule(): SetupSurfaceModule { + return loadBundledEntryExportSync(import.meta.url, { + specifier: "./src/setup-surface.js", + }); +} + +export { zaloDmPolicy, zaloSetupAdapter, createZaloSetupWizardProxy } from "./src/setup-core.js"; +export { evaluateZaloGroupAccess, resolveZaloRuntimeGroupPolicy } from "./src/group-access.js"; + +export const zaloSetupWizard: SetupSurfaceModule["zaloSetupWizard"] = createLazyObjectValue( + () => loadSetupSurfaceModule().zaloSetupWizard as object, +) as SetupSurfaceModule["zaloSetupWizard"]; diff --git a/extensions/zalo/setup-entry.ts b/extensions/zalo/setup-entry.ts index 1fb1c1714a8..80f4af9353d 100644 --- a/extensions/zalo/setup-entry.ts +++ b/extensions/zalo/setup-entry.ts @@ -1,4 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { zaloPlugin } from "./src/channel.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export default defineSetupPluginEntry(zaloPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./runtime-api.js", + exportName: "zaloPlugin", + }, +}); diff --git a/extensions/zalouser/api.ts b/extensions/zalouser/api.ts index 231e6e030b7..235a81d153f 100644 --- a/extensions/zalouser/api.ts +++ b/extensions/zalouser/api.ts @@ -1,3 +1,6 @@ +export { zalouserPlugin } from "./src/channel.js"; +export { zalouserSetupPlugin } from "./src/channel.setup.js"; +export { createZalouserTool } from "./src/tool.js"; export * from "./src/setup-core.js"; export * from "./src/setup-surface.js"; export * from "./src/security-audit.js"; diff --git a/extensions/zalouser/index.ts b/extensions/zalouser/index.ts index c5560a70391..fa28779dd1c 100644 --- a/extensions/zalouser/index.ts +++ b/extensions/zalouser/index.ts @@ -1,17 +1,33 @@ -import { defineChannelPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { zalouserPlugin } from "./src/channel.js"; -import { setZalouserRuntime } from "./src/runtime.js"; -import { createZalouserTool } from "./src/tool.js"; +import { + type AnyAgentTool, + defineBundledChannelEntry, + loadBundledEntryExportSync, +} from "openclaw/plugin-sdk/channel-entry-contract"; -export { zalouserPlugin } from "./src/channel.js"; -export { setZalouserRuntime } from "./src/runtime.js"; +function createZalouserTool(context?: unknown): AnyAgentTool { + const createTool = loadBundledEntryExportSync<(context?: unknown) => AnyAgentTool>( + import.meta.url, + { + specifier: "./api.js", + exportName: "createZalouserTool", + }, + ); + return createTool(context); +} -export default defineChannelPluginEntry({ +export default defineBundledChannelEntry({ id: "zalouser", name: "Zalo Personal", description: "Zalo personal account messaging via native zca-js integration", - plugin: zalouserPlugin, - setRuntime: setZalouserRuntime, + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "zalouserPlugin", + }, + runtime: { + specifier: "./runtime-api.js", + exportName: "setZalouserRuntime", + }, registerFull(api) { api.registerTool((ctx) => createZalouserTool(ctx), { name: "zalouser" }); }, diff --git a/extensions/zalouser/runtime-api.ts b/extensions/zalouser/runtime-api.ts index 1a99487ad54..63759649519 100644 --- a/extensions/zalouser/runtime-api.ts +++ b/extensions/zalouser/runtime-api.ts @@ -2,6 +2,7 @@ // Keep this barrel thin and aligned with the local extension surface. export * from "./api.js"; +export { setZalouserRuntime } from "./src/runtime.js"; export type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; export type { BaseProbeResult, diff --git a/extensions/zalouser/setup-entry.ts b/extensions/zalouser/setup-entry.ts index def0bdd4862..659201537ec 100644 --- a/extensions/zalouser/setup-entry.ts +++ b/extensions/zalouser/setup-entry.ts @@ -1,6 +1,9 @@ -import { defineSetupPluginEntry } from "openclaw/plugin-sdk/channel-core"; -import { zalouserSetupPlugin } from "./src/channel.setup.js"; +import { defineBundledChannelSetupEntry } from "openclaw/plugin-sdk/channel-entry-contract"; -export { zalouserSetupPlugin } from "./src/channel.setup.js"; - -export default defineSetupPluginEntry(zalouserSetupPlugin); +export default defineBundledChannelSetupEntry({ + importMetaUrl: import.meta.url, + plugin: { + specifier: "./api.js", + exportName: "zalouserSetupPlugin", + }, +}); diff --git a/package.json b/package.json index 2b3de268b93..ac3afc0f6c5 100644 --- a/package.json +++ b/package.json @@ -459,6 +459,10 @@ "types": "./dist/plugin-sdk/channel-core.d.ts", "default": "./dist/plugin-sdk/channel-core.js" }, + "./plugin-sdk/channel-entry-contract": { + "types": "./dist/plugin-sdk/channel-entry-contract.d.ts", + "default": "./dist/plugin-sdk/channel-entry-contract.js" + }, "./plugin-sdk/channel-contract": { "types": "./dist/plugin-sdk/channel-contract.d.ts", "default": "./dist/plugin-sdk/channel-contract.js" diff --git a/scripts/lib/plugin-sdk-entrypoints.json b/scripts/lib/plugin-sdk-entrypoints.json index 31704c36be3..f12ad1475b7 100644 --- a/scripts/lib/plugin-sdk-entrypoints.json +++ b/scripts/lib/plugin-sdk-entrypoints.json @@ -104,6 +104,7 @@ "channel-actions", "channel-plugin-common", "channel-core", + "channel-entry-contract", "channel-contract", "channel-feedback", "channel-inbound", diff --git a/src/channels/plugins/bundled.shape-guard.test.ts b/src/channels/plugins/bundled.shape-guard.test.ts index 2a3ea1cd7e5..a7a21a66188 100644 --- a/src/channels/plugins/bundled.shape-guard.test.ts +++ b/src/channels/plugins/bundled.shape-guard.test.ts @@ -34,7 +34,7 @@ describe("bundled channel entry shape guards", () => { expect(bundled.listBundledChannelPlugins()).toEqual([]); expect(bundled.listBundledChannelSetupPlugins()).toEqual([]); }); - it("keeps channel entrypoints on the narrow channel-core SDK surface", () => { + it("keeps channel entrypoints on the dedicated entry-contract SDK surface", () => { const extensionRoot = path.resolve("extensions"); const offenders: string[] = []; @@ -43,18 +43,53 @@ describe("bundled channel entry shape guards", () => { if (!fs.statSync(extensionDir).isDirectory()) { continue; } - for (const relativePath of ["index.ts", "setup-entry.ts"]) { + for (const relativePath of ["index.ts", "channel-entry.ts", "setup-entry.ts"]) { const filePath = path.join(extensionDir, relativePath); if (!fs.existsSync(filePath)) { continue; } const source = fs.readFileSync(filePath, "utf8"); const usesEntryHelpers = - source.includes("defineChannelPluginEntry") || source.includes("defineSetupPluginEntry"); + source.includes("defineBundledChannelEntry") || + source.includes("defineBundledChannelSetupEntry"); if (!usesEntryHelpers) { continue; } - if (source.includes('from "openclaw/plugin-sdk/core"')) { + if ( + !source.includes('from "openclaw/plugin-sdk/channel-entry-contract"') || + source.includes('from "openclaw/plugin-sdk/core"') || + source.includes('from "openclaw/plugin-sdk/channel-core"') + ) { + offenders.push(path.relative(process.cwd(), filePath)); + } + } + } + + expect(offenders).toEqual([]); + }); + + it("keeps bundled channel entrypoints free of static src imports", () => { + const extensionRoot = path.resolve("extensions"); + const offenders: string[] = []; + + for (const extensionId of fs.readdirSync(extensionRoot)) { + const extensionDir = path.join(extensionRoot, extensionId); + if (!fs.statSync(extensionDir).isDirectory()) { + continue; + } + for (const relativePath of ["index.ts", "channel-entry.ts", "setup-entry.ts"]) { + const filePath = path.join(extensionDir, relativePath); + if (!fs.existsSync(filePath)) { + continue; + } + const source = fs.readFileSync(filePath, "utf8"); + const usesEntryHelpers = + source.includes("defineBundledChannelEntry") || + source.includes("defineBundledChannelSetupEntry"); + if (!usesEntryHelpers) { + continue; + } + if (/^(?:import|export)\s.+["']\.\/src\//mu.test(source)) { offenders.push(path.relative(process.cwd(), filePath)); } } @@ -199,11 +234,19 @@ describe("bundled channel entry shape guards", () => { } return { default: { - channelPlugin: { - id: "alpha", - meta: {}, - capabilities: {}, - config: {}, + kind: "bundled-channel-entry", + id: "alpha", + name: "Alpha", + description: "Alpha", + configSchema: {}, + register() {}, + loadChannelPlugin() { + return { + id: "alpha", + meta: {}, + capabilities: {}, + config: {}, + }; }, }, }; diff --git a/src/channels/plugins/bundled.ts b/src/channels/plugins/bundled.ts index 4ebb85b1052..cfac448c8e7 100644 --- a/src/channels/plugins/bundled.ts +++ b/src/channels/plugins/bundled.ts @@ -4,6 +4,10 @@ import path from "node:path"; import { createJiti } from "jiti"; import { openBoundaryFileSync } from "../../infra/boundary-file-read.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; +import type { + BundledChannelEntryContract, + BundledChannelSetupEntryContract, +} from "../../plugin-sdk/channel-entry-contract.js"; import { discoverOpenClawPlugins } from "../../plugins/discovery.js"; import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js"; import type { PluginRuntime } from "../../plugins/runtime/types.js"; @@ -16,13 +20,8 @@ import type { ChannelId, ChannelPlugin } from "./types.js"; type GeneratedBundledChannelEntry = { id: string; - entry: { - channelPlugin: ChannelPlugin; - setChannelRuntime?: (runtime: PluginRuntime) => void; - }; - setupEntry?: { - plugin: ChannelPlugin; - }; + entry: BundledChannelEntryContract; + setupEntry?: BundledChannelSetupEntryContract; }; type BundledChannelDiscoveryCandidate = { @@ -44,38 +43,7 @@ const nodeRequire = createRequire(import.meta.url); function resolveChannelPluginModuleEntry( moduleExport: unknown, -): GeneratedBundledChannelEntry["entry"] | null { - const resolveNamedFallback = (value: unknown): GeneratedBundledChannelEntry["entry"] | null => { - if (!value || typeof value !== "object") { - return null; - } - const entries = Object.entries(value as Record).filter( - ([key]) => key !== "default", - ); - const pluginCandidates = entries.filter( - ([key, candidate]) => - key.endsWith("Plugin") && - !!candidate && - typeof candidate === "object" && - "id" in (candidate as Record), - ); - if (pluginCandidates.length !== 1) { - return null; - } - const runtimeCandidates = entries.filter( - ([key, candidate]) => - key.startsWith("set") && key.endsWith("Runtime") && typeof candidate === "function", - ); - return { - channelPlugin: pluginCandidates[0][1] as ChannelPlugin, - ...(runtimeCandidates.length === 1 - ? { - setChannelRuntime: runtimeCandidates[0][1] as (runtime: PluginRuntime) => void, - } - : {}), - }; - }; - +): BundledChannelEntryContract | null { const resolved = moduleExport && typeof moduleExport === "object" && @@ -85,24 +53,25 @@ function resolveChannelPluginModuleEntry( if (!resolved || typeof resolved !== "object") { return null; } - const record = resolved as { - channelPlugin?: unknown; - setChannelRuntime?: unknown; - }; - if (!record.channelPlugin || typeof record.channelPlugin !== "object") { - return resolveNamedFallback(resolved) ?? resolveNamedFallback(moduleExport); + const record = resolved as Partial; + if (record.kind !== "bundled-channel-entry") { + return null; } - return { - channelPlugin: record.channelPlugin as ChannelPlugin, - ...(typeof record.setChannelRuntime === "function" - ? { setChannelRuntime: record.setChannelRuntime as (runtime: PluginRuntime) => void } - : {}), - }; + if ( + typeof record.id !== "string" || + typeof record.name !== "string" || + typeof record.description !== "string" || + typeof record.register !== "function" || + typeof record.loadChannelPlugin !== "function" + ) { + return null; + } + return record as BundledChannelEntryContract; } function resolveChannelSetupModuleEntry( moduleExport: unknown, -): GeneratedBundledChannelEntry["setupEntry"] | null { +): BundledChannelSetupEntryContract | null { const resolved = moduleExport && typeof moduleExport === "object" && @@ -112,15 +81,14 @@ function resolveChannelSetupModuleEntry( if (!resolved || typeof resolved !== "object") { return null; } - const record = resolved as { - plugin?: unknown; - }; - if (!record.plugin || typeof record.plugin !== "object") { + const record = resolved as Partial; + if (record.kind !== "bundled-channel-setup-entry") { return null; } - return { - plugin: record.plugin as ChannelPlugin, - }; + if (typeof record.loadSetupPlugin !== "function") { + return null; + } + return record as BundledChannelSetupEntryContract; } function createModuleLoader() { @@ -237,7 +205,7 @@ function loadGeneratedBundledChannelEntries(): readonly GeneratedBundledChannelE ); if (!entry) { log.warn( - `[channels] bundled channel entry ${manifest.id} missing channelPlugin export from ${sourcePath}; skipping`, + `[channels] bundled channel entry ${manifest.id} missing bundled-channel-entry contract from ${sourcePath}; skipping`, ); continue; } @@ -281,10 +249,7 @@ type BundledChannelState = { plugins: readonly ChannelPlugin[]; setupPlugins: readonly ChannelPlugin[]; pluginsById: Map; - runtimeSettersById: Map< - ChannelId, - NonNullable - >; + runtimeSettersById: Map>; }; const EMPTY_BUNDLED_CHANNEL_STATE: BundledChannelState = { @@ -307,18 +272,18 @@ function getBundledChannelState(): BundledChannelState { } bundledChannelStateLoadInProgress = true; const entries = loadGeneratedBundledChannelEntries(); - const plugins = entries.map(({ entry }) => entry.channelPlugin); + const plugins = entries.map(({ entry }) => entry.loadChannelPlugin()); const setupPlugins = entries.flatMap(({ setupEntry }) => { - const plugin = setupEntry?.plugin; + const plugin = setupEntry?.loadSetupPlugin(); return plugin ? [plugin] : []; }); const runtimeSettersById = new Map< ChannelId, - NonNullable + NonNullable >(); for (const { entry } of entries) { if (entry.setChannelRuntime) { - runtimeSettersById.set(entry.channelPlugin.id, entry.setChannelRuntime); + runtimeSettersById.set(entry.id, entry.setChannelRuntime); } } diff --git a/src/plugin-sdk/channel-entry-contract.ts b/src/plugin-sdk/channel-entry-contract.ts new file mode 100644 index 00000000000..75333bac9c1 --- /dev/null +++ b/src/plugin-sdk/channel-entry-contract.ts @@ -0,0 +1,227 @@ +import fs from "node:fs"; +import { createRequire } from "node:module"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createJiti } from "jiti"; +import { emptyChannelConfigSchema } from "../channels/plugins/config-schema.js"; +import type { ChannelConfigSchema, ChannelPlugin } from "../channels/plugins/types.plugin.js"; +import { openBoundaryFileSync } from "../infra/boundary-file-read.js"; +import type { PluginRuntime } from "../plugins/runtime/types.js"; +import { + buildPluginLoaderAliasMap, + buildPluginLoaderJitiOptions, + shouldPreferNativeJiti, +} from "../plugins/sdk-alias.js"; +import type { AnyAgentTool, OpenClawPluginApi, PluginCommandContext } from "../plugins/types.js"; + +export type { AnyAgentTool, OpenClawPluginApi, PluginCommandContext }; + +type ChannelEntryConfigSchema = + TPlugin extends ChannelPlugin + ? NonNullable + : ChannelConfigSchema; + +type BundledEntryModuleRef = { + specifier: string; + exportName?: string; +}; + +type DefineBundledChannelEntryOptions = { + id: string; + name: string; + description: string; + importMetaUrl: string; + plugin: BundledEntryModuleRef; + configSchema?: ChannelEntryConfigSchema | (() => ChannelEntryConfigSchema); + runtime?: BundledEntryModuleRef; + registerCliMetadata?: (api: OpenClawPluginApi) => void; + registerFull?: (api: OpenClawPluginApi) => void; +}; + +type DefineBundledChannelSetupEntryOptions = { + importMetaUrl: string; + plugin: BundledEntryModuleRef; +}; + +export type BundledChannelEntryContract = { + kind: "bundled-channel-entry"; + id: string; + name: string; + description: string; + configSchema: ChannelEntryConfigSchema; + register: (api: OpenClawPluginApi) => void; + loadChannelPlugin: () => TPlugin; + setChannelRuntime?: (runtime: PluginRuntime) => void; +}; + +export type BundledChannelSetupEntryContract = { + kind: "bundled-channel-setup-entry"; + loadSetupPlugin: () => TPlugin; +}; + +const nodeRequire = createRequire(import.meta.url); +const jitiLoaders = new Map>(); +const loadedModuleExports = new Map(); + +function resolveSpecifierCandidates(modulePath: string): string[] { + const ext = path.extname(modulePath).toLowerCase(); + if (ext === ".js") { + return [modulePath, modulePath.slice(0, -3) + ".ts"]; + } + if (ext === ".mjs") { + return [modulePath, modulePath.slice(0, -4) + ".mts"]; + } + if (ext === ".cjs") { + return [modulePath, modulePath.slice(0, -4) + ".cts"]; + } + return [modulePath]; +} + +function resolveEntryBoundaryRoot(importMetaUrl: string): string { + return path.dirname(fileURLToPath(importMetaUrl)); +} + +function resolveBundledEntryModulePath(importMetaUrl: string, specifier: string): string { + const importerPath = fileURLToPath(importMetaUrl); + const resolved = path.resolve(path.dirname(importerPath), specifier); + const boundaryRoot = resolveEntryBoundaryRoot(importMetaUrl); + const candidate = + resolveSpecifierCandidates(resolved).find((entry) => fs.existsSync(entry)) ?? resolved; + const opened = openBoundaryFileSync({ + absolutePath: candidate, + rootPath: boundaryRoot, + boundaryLabel: "plugin root", + rejectHardlinks: false, + skipLexicalRootCheck: true, + }); + if (!opened.ok) { + throw new Error(`plugin entry path escapes plugin root: ${specifier}`); + } + fs.closeSync(opened.fd); + return opened.path; +} + +function getJiti(modulePath: string) { + const tryNative = + shouldPreferNativeJiti(modulePath) || modulePath.includes(`${path.sep}dist${path.sep}`); + const aliasMap = buildPluginLoaderAliasMap(modulePath, process.argv[1], import.meta.url); + const cacheKey = JSON.stringify({ + tryNative, + aliasMap: Object.entries(aliasMap).toSorted(([left], [right]) => left.localeCompare(right)), + }); + const cached = jitiLoaders.get(cacheKey); + if (cached) { + return cached; + } + const loader = createJiti(import.meta.url, { + ...buildPluginLoaderJitiOptions(aliasMap), + tryNative, + }); + jitiLoaders.set(cacheKey, loader); + return loader; +} + +function loadBundledEntryModuleSync(importMetaUrl: string, specifier: string): unknown { + const modulePath = resolveBundledEntryModulePath(importMetaUrl, specifier); + const cached = loadedModuleExports.get(modulePath); + if (cached !== undefined) { + return cached; + } + let loaded: unknown; + if ( + process.platform === "win32" && + modulePath.includes(`${path.sep}dist${path.sep}`) && + [".js", ".mjs", ".cjs"].includes(path.extname(modulePath).toLowerCase()) + ) { + try { + loaded = nodeRequire(modulePath); + } catch { + loaded = getJiti(modulePath)(modulePath); + } + } else { + loaded = getJiti(modulePath)(modulePath); + } + loadedModuleExports.set(modulePath, loaded); + return loaded; +} + +export function loadBundledEntryExportSync( + importMetaUrl: string, + reference: BundledEntryModuleRef, +): T { + const loaded = loadBundledEntryModuleSync(importMetaUrl, reference.specifier); + const resolved = + loaded && typeof loaded === "object" && "default" in (loaded as Record) + ? (loaded as { default: unknown }).default + : loaded; + if (!reference.exportName) { + return resolved as T; + } + const record = (resolved ?? loaded) as Record | undefined; + if (!record || !(reference.exportName in record)) { + throw new Error( + `missing export "${reference.exportName}" from bundled entry module ${reference.specifier}`, + ); + } + return record[reference.exportName] as T; +} + +export function defineBundledChannelEntry({ + id, + name, + description, + importMetaUrl, + plugin, + configSchema, + runtime, + registerCliMetadata, + registerFull, +}: DefineBundledChannelEntryOptions): BundledChannelEntryContract { + const resolvedConfigSchema: ChannelEntryConfigSchema = + typeof configSchema === "function" + ? configSchema() + : ((configSchema ?? emptyChannelConfigSchema()) as ChannelEntryConfigSchema); + const loadChannelPlugin = () => loadBundledEntryExportSync(importMetaUrl, plugin); + const setChannelRuntime = runtime + ? (pluginRuntime: PluginRuntime) => { + const setter = loadBundledEntryExportSync<(runtime: PluginRuntime) => void>( + importMetaUrl, + runtime, + ); + setter(pluginRuntime); + } + : undefined; + + return { + kind: "bundled-channel-entry", + id, + name, + description, + configSchema: resolvedConfigSchema, + register(api: OpenClawPluginApi) { + if (api.registrationMode === "cli-metadata") { + registerCliMetadata?.(api); + return; + } + setChannelRuntime?.(api.runtime); + api.registerChannel({ plugin: loadChannelPlugin() as ChannelPlugin }); + if (api.registrationMode !== "full") { + return; + } + registerCliMetadata?.(api); + registerFull?.(api); + }, + loadChannelPlugin, + ...(setChannelRuntime ? { setChannelRuntime } : {}), + }; +} + +export function defineBundledChannelSetupEntry({ + importMetaUrl, + plugin, +}: DefineBundledChannelSetupEntryOptions): BundledChannelSetupEntryContract { + return { + kind: "bundled-channel-setup-entry", + loadSetupPlugin: () => loadBundledEntryExportSync(importMetaUrl, plugin), + }; +} diff --git a/src/plugin-sdk/zalo-setup.ts b/src/plugin-sdk/zalo-setup.ts index 5461a416790..8475d8dedb1 100644 --- a/src/plugin-sdk/zalo-setup.ts +++ b/src/plugin-sdk/zalo-setup.ts @@ -1,5 +1,5 @@ // Manual facade. Keep loader boundary explicit. -type FacadeModule = typeof import("@openclaw/zalo/api.js"); +type FacadeModule = typeof import("@openclaw/zalo/setup-api.js"); import { createLazyFacadeObjectValue, loadBundledPluginPublicSurfaceModuleSync, @@ -8,7 +8,7 @@ import { function loadFacadeModule(): FacadeModule { return loadBundledPluginPublicSurfaceModuleSync({ dirName: "zalo", - artifactBasename: "api.js", + artifactBasename: "setup-api.js", }); } export const evaluateZaloGroupAccess: FacadeModule["evaluateZaloGroupAccess"] = ((...args) =>