From 1278ee92480390d6b73d8e769967b6ffd1ada58c Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Tue, 3 Mar 2026 22:07:03 -0500 Subject: [PATCH] plugin-sdk: add channel subpaths and migrate bundled plugins --- CHANGELOG.md | 1 + docs/tools/plugin.md | 20 +++ extensions/acpx/index.ts | 2 +- extensions/bluebubbles/index.ts | 4 +- extensions/copilot-proxy/index.ts | 2 +- extensions/device-pair/notify.ts | 4 +- extensions/diagnostics-otel/index.ts | 4 +- extensions/diffs/index.ts | 4 +- extensions/discord/index.ts | 4 +- extensions/discord/src/channel.test.ts | 2 +- extensions/discord/src/channel.ts | 2 +- extensions/discord/src/runtime.ts | 2 +- extensions/discord/src/subagent-hooks.test.ts | 4 +- extensions/discord/src/subagent-hooks.ts | 4 +- extensions/feishu/index.ts | 4 +- extensions/google-gemini-cli-auth/index.ts | 2 +- extensions/googlechat/index.ts | 4 +- extensions/googlechat/package.json | 2 +- extensions/imessage/index.ts | 4 +- extensions/imessage/src/channel.ts | 2 +- extensions/imessage/src/runtime.ts | 2 +- extensions/irc/index.ts | 4 +- extensions/line/index.ts | 4 +- extensions/line/src/card-command.ts | 4 +- extensions/line/src/channel.logout.test.ts | 2 +- .../line/src/channel.sendPayload.test.ts | 2 +- extensions/line/src/channel.startup.test.ts | 2 +- extensions/line/src/channel.ts | 2 +- extensions/line/src/runtime.ts | 2 +- extensions/matrix/index.ts | 4 +- extensions/mattermost/index.ts | 4 +- extensions/memory-core/package.json | 2 +- extensions/memory-lancedb/index.ts | 2 +- extensions/minimax-portal-auth/index.ts | 2 +- extensions/msteams/index.ts | 4 +- extensions/nextcloud-talk/index.ts | 4 +- extensions/nostr/index.ts | 4 +- extensions/qwen-portal-auth/index.ts | 2 +- extensions/signal/index.ts | 4 +- extensions/signal/src/channel.ts | 2 +- extensions/signal/src/runtime.ts | 2 +- extensions/slack/index.ts | 4 +- extensions/slack/src/channel.test.ts | 2 +- extensions/slack/src/channel.ts | 2 +- extensions/slack/src/runtime.ts | 2 +- extensions/synology-chat/index.ts | 4 +- extensions/thread-ownership/index.ts | 2 +- extensions/tlon/index.ts | 4 +- extensions/twitch/index.ts | 4 +- extensions/voice-call/index.ts | 2 +- extensions/whatsapp/index.ts | 4 +- extensions/whatsapp/src/channel.ts | 2 +- .../whatsapp/src/resolve-target.test.ts | 118 +++++++----------- extensions/whatsapp/src/runtime.ts | 2 +- extensions/zalo/index.ts | 4 +- extensions/zalouser/index.ts | 4 +- package.json | 27 +++- pnpm-lock.yaml | 62 ++++++--- ...-no-monolithic-plugin-sdk-entry-imports.ts | 50 ++++++++ scripts/check-plugin-sdk-exports.mjs | 31 ++++- scripts/release-check.ts | 16 +++ scripts/write-plugin-sdk-entry-dts.ts | 13 +- src/plugin-sdk/core.ts | 12 +- src/plugin-sdk/discord.ts | 60 +++++++++ src/plugin-sdk/imessage.ts | 49 ++++++++ src/plugin-sdk/line.ts | 36 ++++++ src/plugin-sdk/signal.ts | 49 ++++++++ src/plugin-sdk/slack.ts | 52 ++++++++ src/plugin-sdk/subpaths.test.ts | 39 ++++++ src/plugin-sdk/whatsapp.ts | 58 +++++++++ src/plugins/loader.test.ts | 23 ++++ src/plugins/loader.ts | 36 ++++++ tsconfig.plugin-sdk.dts.json | 6 + tsdown.config.ts | 42 +++++++ vitest.config.ts | 24 ++++ 75 files changed, 808 insertions(+), 174 deletions(-) create mode 100644 scripts/check-no-monolithic-plugin-sdk-entry-imports.ts create mode 100644 src/plugin-sdk/discord.ts create mode 100644 src/plugin-sdk/imessage.ts create mode 100644 src/plugin-sdk/line.ts create mode 100644 src/plugin-sdk/signal.ts create mode 100644 src/plugin-sdk/slack.ts create mode 100644 src/plugin-sdk/subpaths.test.ts create mode 100644 src/plugin-sdk/whatsapp.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c29db34273e..dcef30232ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Docs: https://docs.openclaw.ai - Plugins/startup loading: lazily initialize plugin runtime, split startup-critical plugin SDK imports into `openclaw/plugin-sdk/core` and `openclaw/plugin-sdk/telegram`, and preserve `api.runtime` reflection semantics for plugin compatibility. (#28620) thanks @hmemcpy. - Build/lazy runtime boundaries: replace ineffective dynamic import sites with dedicated lazy runtime boundaries across Slack slash handling, Telegram audit, CLI send deps, memory fallback, and outbound delivery paths while preserving behavior. (#33690) thanks @gumadeiras. - Config/heartbeat legacy-path handling: auto-migrate top-level `heartbeat` into `agents.defaults.heartbeat` (with merge semantics that preserve explicit defaults), and keep startup failures on non-migratable legacy entries in the detailed invalid-config path instead of generic migration-failed errors. (#32706) thanks @xiwan. +- Plugins/SDK subpath parity: add channel-specific plugin SDK subpaths for Discord, Slack, Signal, iMessage, WhatsApp, and LINE; migrate bundled plugin entrypoints to scoped subpaths/core with CI guardrails; and keep `openclaw/plugin-sdk` root import compatibility for existing external plugins. (#33737) thanks @gumadeiras. - Security/auth labels: remove token and API-key snippets from user-facing auth status labels so `/status` and `/models` do not expose credential fragments. (#33262) thanks @cu1ch3n. - Auth/credential semantics: align profile eligibility + probe diagnostics with SecretRef/expiry rules and harden browser download atomic writes. (#33733) thanks @joshavant. - Security/audit denyCommands guidance: suggest likely exact node command IDs for unknown `gateway.nodes.denyCommands` entries so ineffective denylist entries are easier to correct. (#29713) thanks @liquidhorizon88-bot. diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index 90e1f461f4c..f5fd5a34ab6 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -106,6 +106,26 @@ Notes: - Uses core media-understanding audio configuration (`tools.media.audio`) and provider fallback order. - Returns `{ text: undefined }` when no transcription output is produced (for example skipped/unsupported input). +## Plugin SDK import paths + +Use SDK subpaths instead of the monolithic `openclaw/plugin-sdk` import when +authoring plugins: + +- `openclaw/plugin-sdk/core` for generic plugin APIs, provider auth types, and shared helpers. +- `openclaw/plugin-sdk/telegram` for Telegram channel plugins. +- `openclaw/plugin-sdk/discord` for Discord channel plugins. +- `openclaw/plugin-sdk/slack` for Slack channel plugins. +- `openclaw/plugin-sdk/signal` for Signal channel plugins. +- `openclaw/plugin-sdk/imessage` for iMessage channel plugins. +- `openclaw/plugin-sdk/whatsapp` for WhatsApp channel plugins. +- `openclaw/plugin-sdk/line` for LINE channel plugins. + +Compatibility note: + +- `openclaw/plugin-sdk` remains supported for existing external plugins. +- New and migrated bundled plugins should use channel subpaths (or `core`) to + keep startup imports scoped. + ## Discovery & precedence OpenClaw scans, in order: diff --git a/extensions/acpx/index.ts b/extensions/acpx/index.ts index 5f57e396f80..187dbacd765 100644 --- a/extensions/acpx/index.ts +++ b/extensions/acpx/index.ts @@ -1,4 +1,4 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; import { createAcpxPluginConfigSchema } from "./src/config.js"; import { createAcpxRuntimeService } from "./src/service.js"; diff --git a/extensions/bluebubbles/index.ts b/extensions/bluebubbles/index.ts index 92bacb8d51a..15d583bd342 100644 --- a/extensions/bluebubbles/index.ts +++ b/extensions/bluebubbles/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { bluebubblesPlugin } from "./src/channel.js"; import { setBlueBubblesRuntime } from "./src/runtime.js"; diff --git a/extensions/copilot-proxy/index.ts b/extensions/copilot-proxy/index.ts index b14684ab552..990752782e7 100644 --- a/extensions/copilot-proxy/index.ts +++ b/extensions/copilot-proxy/index.ts @@ -3,7 +3,7 @@ import { type OpenClawPluginApi, type ProviderAuthContext, type ProviderAuthResult, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/core"; const DEFAULT_BASE_URL = "http://localhost:3000/v1"; const DEFAULT_API_KEY = "n/a"; diff --git a/extensions/device-pair/notify.ts b/extensions/device-pair/notify.ts index 3430a89cfa4..da43d2dc273 100644 --- a/extensions/device-pair/notify.ts +++ b/extensions/device-pair/notify.ts @@ -1,7 +1,7 @@ import { promises as fs } from "node:fs"; import path from "node:path"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { listDevicePairing } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { listDevicePairing } from "openclaw/plugin-sdk/core"; const NOTIFY_STATE_FILE = "device-pair-notify.json"; const NOTIFY_POLL_INTERVAL_MS = 10_000; diff --git a/extensions/diagnostics-otel/index.ts b/extensions/diagnostics-otel/index.ts index 0b9c5318def..4c460a125d8 100644 --- a/extensions/diagnostics-otel/index.ts +++ b/extensions/diagnostics-otel/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { createDiagnosticsOtelService } from "./src/service.js"; const plugin = { diff --git a/extensions/diffs/index.ts b/extensions/diffs/index.ts index 945448656e2..ccd3ef77b5a 100644 --- a/extensions/diffs/index.ts +++ b/extensions/diffs/index.ts @@ -1,6 +1,6 @@ import path from "node:path"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/core"; import { diffsPluginConfigSchema, resolveDiffsPluginDefaults, diff --git a/extensions/discord/index.ts b/extensions/discord/index.ts index dcddde67c86..ad441b09bc1 100644 --- a/extensions/discord/index.ts +++ b/extensions/discord/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/discord"; import { discordPlugin } from "./src/channel.js"; import { setDiscordRuntime } from "./src/runtime.js"; import { registerDiscordSubagentHooks } from "./src/subagent-hooks.js"; diff --git a/extensions/discord/src/channel.test.ts b/extensions/discord/src/channel.test.ts index b5981e77d93..0a4ead6c3fd 100644 --- a/extensions/discord/src/channel.test.ts +++ b/extensions/discord/src/channel.test.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk"; +import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/discord"; import { describe, expect, it, vi } from "vitest"; import { discordPlugin } from "./channel.js"; import { setDiscordRuntime } from "./runtime.js"; diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index 3a36a61171d..bfc2b92db74 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -29,7 +29,7 @@ import { type ChannelMessageActionAdapter, type ChannelPlugin, type ResolvedDiscordAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/discord"; import { getDiscordRuntime } from "./runtime.js"; const meta = getChatChannelMeta("discord"); diff --git a/extensions/discord/src/runtime.ts b/extensions/discord/src/runtime.ts index 5c3aa9f3676..506a81085ee 100644 --- a/extensions/discord/src/runtime.ts +++ b/extensions/discord/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/discord"; let runtime: PluginRuntime | null = null; diff --git a/extensions/discord/src/subagent-hooks.test.ts b/extensions/discord/src/subagent-hooks.test.ts index f8a139cd56d..d58f07c1314 100644 --- a/extensions/discord/src/subagent-hooks.test.ts +++ b/extensions/discord/src/subagent-hooks.test.ts @@ -1,4 +1,4 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { registerDiscordSubagentHooks } from "./subagent-hooks.js"; @@ -35,7 +35,7 @@ const hookMocks = vi.hoisted(() => ({ unbindThreadBindingsBySessionKey: vi.fn(() => []), })); -vi.mock("openclaw/plugin-sdk", () => ({ +vi.mock("openclaw/plugin-sdk/discord", () => ({ resolveDiscordAccount: hookMocks.resolveDiscordAccount, autoBindSpawnedDiscordSubagent: hookMocks.autoBindSpawnedDiscordSubagent, listThreadBindingsBySessionKey: hookMocks.listThreadBindingsBySessionKey, diff --git a/extensions/discord/src/subagent-hooks.ts b/extensions/discord/src/subagent-hooks.ts index 8ecd7873d88..f6e6056538b 100644 --- a/extensions/discord/src/subagent-hooks.ts +++ b/extensions/discord/src/subagent-hooks.ts @@ -1,10 +1,10 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/discord"; import { autoBindSpawnedDiscordSubagent, listThreadBindingsBySessionKey, resolveDiscordAccount, unbindThreadBindingsBySessionKey, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/discord"; function summarizeError(err: unknown): string { if (err instanceof Error) { diff --git a/extensions/feishu/index.ts b/extensions/feishu/index.ts index 5cb75ec6483..62f311262d7 100644 --- a/extensions/feishu/index.ts +++ b/extensions/feishu/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { registerFeishuBitableTools } from "./src/bitable.js"; import { feishuPlugin } from "./src/channel.js"; import { registerFeishuChatTools } from "./src/chat.js"; diff --git a/extensions/google-gemini-cli-auth/index.ts b/extensions/google-gemini-cli-auth/index.ts index 89b7c4d1cfb..254b3994bd5 100644 --- a/extensions/google-gemini-cli-auth/index.ts +++ b/extensions/google-gemini-cli-auth/index.ts @@ -3,7 +3,7 @@ import { emptyPluginConfigSchema, type OpenClawPluginApi, type ProviderAuthContext, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/core"; import { loginGeminiCliOAuth } from "./oauth.js"; const PROVIDER_ID = "google-gemini-cli"; diff --git a/extensions/googlechat/index.ts b/extensions/googlechat/index.ts index c5acead0f61..8bcb1f76e3a 100644 --- a/extensions/googlechat/index.ts +++ b/extensions/googlechat/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { googlechatDock, googlechatPlugin } from "./src/channel.js"; import { setGoogleChatRuntime } from "./src/runtime.js"; diff --git a/extensions/googlechat/package.json b/extensions/googlechat/package.json index 7506b44171d..ed7ba487ce0 100644 --- a/extensions/googlechat/package.json +++ b/extensions/googlechat/package.json @@ -8,7 +8,7 @@ "google-auth-library": "^10.6.1" }, "peerDependencies": { - "openclaw": ">=2026.3.1" + "openclaw": ">=2026.3.2" }, "openclaw": { "extensions": [ diff --git a/extensions/imessage/index.ts b/extensions/imessage/index.ts index 7eb0e80b070..cf0c6b3d8bd 100644 --- a/extensions/imessage/index.ts +++ b/extensions/imessage/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/imessage"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/imessage"; import { imessagePlugin } from "./src/channel.js"; import { setIMessageRuntime } from "./src/runtime.js"; diff --git a/extensions/imessage/src/channel.ts b/extensions/imessage/src/channel.ts index 36963ca981f..994df82c73f 100644 --- a/extensions/imessage/src/channel.ts +++ b/extensions/imessage/src/channel.ts @@ -26,7 +26,7 @@ import { setAccountEnabledInConfigSection, type ChannelPlugin, type ResolvedIMessageAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/imessage"; import { getIMessageRuntime } from "./runtime.js"; const meta = getChatChannelMeta("imessage"); diff --git a/extensions/imessage/src/runtime.ts b/extensions/imessage/src/runtime.ts index ed41c9cb809..866d9c8d380 100644 --- a/extensions/imessage/src/runtime.ts +++ b/extensions/imessage/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/imessage"; let runtime: PluginRuntime | null = null; diff --git a/extensions/irc/index.ts b/extensions/irc/index.ts index 2a64cbe8650..6c5e19f16e3 100644 --- a/extensions/irc/index.ts +++ b/extensions/irc/index.ts @@ -1,5 +1,5 @@ -import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { ChannelPlugin, OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { ircPlugin } from "./src/channel.js"; import { setIrcRuntime } from "./src/runtime.js"; diff --git a/extensions/line/index.ts b/extensions/line/index.ts index 3d90029c27b..961baf1f01b 100644 --- a/extensions/line/index.ts +++ b/extensions/line/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/line"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/line"; import { registerLineCardCommand } from "./src/card-command.js"; import { linePlugin } from "./src/channel.js"; import { setLineRuntime } from "./src/runtime.js"; diff --git a/extensions/line/src/card-command.ts b/extensions/line/src/card-command.ts index ff113b75e0a..cc5ec78eeab 100644 --- a/extensions/line/src/card-command.ts +++ b/extensions/line/src/card-command.ts @@ -1,4 +1,4 @@ -import type { LineChannelData, OpenClawPluginApi, ReplyPayload } from "openclaw/plugin-sdk"; +import type { LineChannelData, OpenClawPluginApi, ReplyPayload } from "openclaw/plugin-sdk/line"; import { createActionCard, createImageCard, @@ -7,7 +7,7 @@ import { createReceiptCard, type CardAction, type ListItem, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/line"; const CARD_USAGE = `Usage: /card "title" "body" [options] diff --git a/extensions/line/src/channel.logout.test.ts b/extensions/line/src/channel.logout.test.ts index b11bdc99870..b10d484fbb1 100644 --- a/extensions/line/src/channel.logout.test.ts +++ b/extensions/line/src/channel.logout.test.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount } from "openclaw/plugin-sdk"; +import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount } from "openclaw/plugin-sdk/line"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; import { linePlugin } from "./channel.js"; diff --git a/extensions/line/src/channel.sendPayload.test.ts b/extensions/line/src/channel.sendPayload.test.ts index 3f91f27c51f..e92551538e9 100644 --- a/extensions/line/src/channel.sendPayload.test.ts +++ b/extensions/line/src/channel.sendPayload.test.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk"; +import type { OpenClawConfig, PluginRuntime } from "openclaw/plugin-sdk/line"; import { describe, expect, it, vi } from "vitest"; import { linePlugin } from "./channel.js"; import { setLineRuntime } from "./runtime.js"; diff --git a/extensions/line/src/channel.startup.test.ts b/extensions/line/src/channel.startup.test.ts index 09722277b17..e4de0f38e3b 100644 --- a/extensions/line/src/channel.startup.test.ts +++ b/extensions/line/src/channel.startup.test.ts @@ -4,7 +4,7 @@ import type { OpenClawConfig, PluginRuntime, ResolvedLineAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/line"; import { describe, expect, it, vi } from "vitest"; import { createRuntimeEnv } from "../../test-utils/runtime-env.js"; import { linePlugin } from "./channel.js"; diff --git a/extensions/line/src/channel.ts b/extensions/line/src/channel.ts index 1c87ad8e2f3..f5a0f9de107 100644 --- a/extensions/line/src/channel.ts +++ b/extensions/line/src/channel.ts @@ -12,7 +12,7 @@ import { type LineConfig, type LineChannelData, type ResolvedLineAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/line"; import { getLineRuntime } from "./runtime.js"; // LINE channel metadata diff --git a/extensions/line/src/runtime.ts b/extensions/line/src/runtime.ts index a352dfccdb8..4f1a4fc121a 100644 --- a/extensions/line/src/runtime.ts +++ b/extensions/line/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/line"; let runtime: PluginRuntime | null = null; diff --git a/extensions/matrix/index.ts b/extensions/matrix/index.ts index f86706d53f5..320b256d3a2 100644 --- a/extensions/matrix/index.ts +++ b/extensions/matrix/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { matrixPlugin } from "./src/channel.js"; import { ensureMatrixCryptoRuntime } from "./src/matrix/deps.js"; import { setMatrixRuntime } from "./src/runtime.js"; diff --git a/extensions/mattermost/index.ts b/extensions/mattermost/index.ts index ae32fb61f77..75b28cc1559 100644 --- a/extensions/mattermost/index.ts +++ b/extensions/mattermost/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { mattermostPlugin } from "./src/channel.js"; import { getSlashCommandState, registerSlashCommandRoute } from "./src/mattermost/slash-state.js"; import { setMattermostRuntime } from "./src/runtime.js"; diff --git a/extensions/memory-core/package.json b/extensions/memory-core/package.json index 480e3b23f02..3669df92fb1 100644 --- a/extensions/memory-core/package.json +++ b/extensions/memory-core/package.json @@ -5,7 +5,7 @@ "description": "OpenClaw core memory search plugin", "type": "module", "peerDependencies": { - "openclaw": ">=2026.3.1" + "openclaw": ">=2026.3.2" }, "openclaw": { "extensions": [ diff --git a/extensions/memory-lancedb/index.ts b/extensions/memory-lancedb/index.ts index f02115b1bf6..cbed48dd9ef 100644 --- a/extensions/memory-lancedb/index.ts +++ b/extensions/memory-lancedb/index.ts @@ -10,7 +10,7 @@ import { randomUUID } from "node:crypto"; import type * as LanceDB from "@lancedb/lancedb"; import { Type } from "@sinclair/typebox"; import OpenAI from "openai"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; import { DEFAULT_CAPTURE_MAX_CHARS, MEMORY_CATEGORIES, diff --git a/extensions/minimax-portal-auth/index.ts b/extensions/minimax-portal-auth/index.ts index 51c1b6e1ec1..731404eb867 100644 --- a/extensions/minimax-portal-auth/index.ts +++ b/extensions/minimax-portal-auth/index.ts @@ -3,7 +3,7 @@ import { type OpenClawPluginApi, type ProviderAuthContext, type ProviderAuthResult, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/core"; import { loginMiniMaxPortalOAuth, type MiniMaxRegion } from "./oauth.js"; const PROVIDER_ID = "minimax-portal"; diff --git a/extensions/msteams/index.ts b/extensions/msteams/index.ts index 6bab4723675..9d5fde61d4d 100644 --- a/extensions/msteams/index.ts +++ b/extensions/msteams/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { msteamsPlugin } from "./src/channel.js"; import { setMSTeamsRuntime } from "./src/runtime.js"; diff --git a/extensions/nextcloud-talk/index.ts b/extensions/nextcloud-talk/index.ts index 1dc9c2d646c..92e68fdcfb7 100644 --- a/extensions/nextcloud-talk/index.ts +++ b/extensions/nextcloud-talk/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { nextcloudTalkPlugin } from "./src/channel.js"; import { setNextcloudTalkRuntime } from "./src/runtime.js"; diff --git a/extensions/nostr/index.ts b/extensions/nostr/index.ts index de9c6e2276d..bcebb2fc06a 100644 --- a/extensions/nostr/index.ts +++ b/extensions/nostr/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { nostrPlugin } from "./src/channel.js"; import type { NostrProfile } from "./src/config-schema.js"; import { createNostrProfileHttpHandler } from "./src/nostr-profile-http.js"; diff --git a/extensions/qwen-portal-auth/index.ts b/extensions/qwen-portal-auth/index.ts index 541dd750e1d..6cbbe8dd9c8 100644 --- a/extensions/qwen-portal-auth/index.ts +++ b/extensions/qwen-portal-auth/index.ts @@ -2,7 +2,7 @@ import { emptyPluginConfigSchema, type OpenClawPluginApi, type ProviderAuthContext, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/core"; import { loginQwenPortalOAuth } from "./oauth.js"; const PROVIDER_ID = "qwen-portal"; diff --git a/extensions/signal/index.ts b/extensions/signal/index.ts index e1069e466e2..0a7b988d7f0 100644 --- a/extensions/signal/index.ts +++ b/extensions/signal/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/signal"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/signal"; import { signalPlugin } from "./src/channel.js"; import { setSignalRuntime } from "./src/runtime.js"; diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index 9a7a9aee13b..44f0bd43294 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -27,7 +27,7 @@ import { type ChannelMessageActionAdapter, type ChannelPlugin, type ResolvedSignalAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/signal"; import { getSignalRuntime } from "./runtime.js"; const signalMessageActions: ChannelMessageActionAdapter = { diff --git a/extensions/signal/src/runtime.ts b/extensions/signal/src/runtime.ts index 8bc1d5e9e8d..21f90071ad8 100644 --- a/extensions/signal/src/runtime.ts +++ b/extensions/signal/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/signal"; let runtime: PluginRuntime | null = null; diff --git a/extensions/slack/index.ts b/extensions/slack/index.ts index 6f5945616c7..57d855141be 100644 --- a/extensions/slack/index.ts +++ b/extensions/slack/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/slack"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/slack"; import { slackPlugin } from "./src/channel.js"; import { setSlackRuntime } from "./src/runtime.js"; diff --git a/extensions/slack/src/channel.test.ts b/extensions/slack/src/channel.test.ts index 4e04d6cf3b7..006054f0930 100644 --- a/extensions/slack/src/channel.test.ts +++ b/extensions/slack/src/channel.test.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig } from "openclaw/plugin-sdk"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/slack"; import { describe, expect, it, vi } from "vitest"; const handleSlackActionMock = vi.fn(); diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index 6af8b382170..f5b073dc045 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -29,7 +29,7 @@ import { SlackConfigSchema, type ChannelPlugin, type ResolvedSlackAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/slack"; import { getSlackRuntime } from "./runtime.js"; const meta = getChatChannelMeta("slack"); diff --git a/extensions/slack/src/runtime.ts b/extensions/slack/src/runtime.ts index 46777871f1a..02222d2b073 100644 --- a/extensions/slack/src/runtime.ts +++ b/extensions/slack/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/slack"; let runtime: PluginRuntime | null = null; diff --git a/extensions/synology-chat/index.ts b/extensions/synology-chat/index.ts index 6b85059761a..87b752bbb33 100644 --- a/extensions/synology-chat/index.ts +++ b/extensions/synology-chat/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { createSynologyChatPlugin } from "./src/channel.js"; import { setSynologyRuntime } from "./src/runtime.js"; diff --git a/extensions/thread-ownership/index.ts b/extensions/thread-ownership/index.ts index 3db1ea94ff4..1960b067f28 100644 --- a/extensions/thread-ownership/index.ts +++ b/extensions/thread-ownership/index.ts @@ -1,4 +1,4 @@ -import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/core"; type ThreadOwnershipConfig = { forwarderUrl?: string; diff --git a/extensions/tlon/index.ts b/extensions/tlon/index.ts index 1cbcd35bc4c..5179c74c61d 100644 --- a/extensions/tlon/index.ts +++ b/extensions/tlon/index.ts @@ -2,8 +2,8 @@ import { spawn } from "node:child_process"; import { existsSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { tlonPlugin } from "./src/channel.js"; import { setTlonRuntime } from "./src/runtime.js"; diff --git a/extensions/twitch/index.ts b/extensions/twitch/index.ts index 992e7f3ea24..7cf7b7f85e8 100644 --- a/extensions/twitch/index.ts +++ b/extensions/twitch/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { twitchPlugin } from "./src/plugin.js"; import { setTwitchRuntime } from "./src/runtime.js"; diff --git a/extensions/voice-call/index.ts b/extensions/voice-call/index.ts index 0aadec4e18b..1e97ec5fac3 100644 --- a/extensions/voice-call/index.ts +++ b/extensions/voice-call/index.ts @@ -1,5 +1,5 @@ import { Type } from "@sinclair/typebox"; -import type { GatewayRequestHandlerOptions, OpenClawPluginApi } from "openclaw/plugin-sdk"; +import type { GatewayRequestHandlerOptions, OpenClawPluginApi } from "openclaw/plugin-sdk/core"; import { registerVoiceCallCli } from "./src/cli.js"; import { VoiceCallConfigSchema, diff --git a/extensions/whatsapp/index.ts b/extensions/whatsapp/index.ts index 1b19ff6775d..9279a2c038d 100644 --- a/extensions/whatsapp/index.ts +++ b/extensions/whatsapp/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/whatsapp"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/whatsapp"; import { whatsappPlugin } from "./src/channel.js"; import { setWhatsAppRuntime } from "./src/runtime.js"; diff --git a/extensions/whatsapp/src/channel.ts b/extensions/whatsapp/src/channel.ts index 67d270d093e..ef36857d899 100644 --- a/extensions/whatsapp/src/channel.ts +++ b/extensions/whatsapp/src/channel.ts @@ -33,7 +33,7 @@ import { type ChannelMessageActionName, type ChannelPlugin, type ResolvedWhatsAppAccount, -} from "openclaw/plugin-sdk"; +} from "openclaw/plugin-sdk/whatsapp"; import { getWhatsAppRuntime } from "./runtime.js"; const meta = getChatChannelMeta("whatsapp"); diff --git a/extensions/whatsapp/src/resolve-target.test.ts b/extensions/whatsapp/src/resolve-target.test.ts index 51bcd15bad3..b0ed25e4dc9 100644 --- a/extensions/whatsapp/src/resolve-target.test.ts +++ b/extensions/whatsapp/src/resolve-target.test.ts @@ -1,82 +1,60 @@ import { describe, expect, it, vi } from "vitest"; import { installCommonResolveTargetErrorCases } from "../../shared/resolve-target-test-helpers.js"; -vi.mock("openclaw/plugin-sdk", () => ({ - getChatChannelMeta: () => ({ id: "whatsapp", label: "WhatsApp" }), - normalizeWhatsAppTarget: (value: string) => { +vi.mock("openclaw/plugin-sdk/whatsapp", async () => { + const actual = await vi.importActual( + "openclaw/plugin-sdk/whatsapp", + ); + const normalizeWhatsAppTarget = (value: string) => { if (value === "invalid-target") return null; - // Simulate E.164 normalization: strip leading + and whatsapp: prefix + // Simulate E.164 normalization: strip leading + and whatsapp: prefix. const stripped = value.replace(/^whatsapp:/i, "").replace(/^\+/, ""); return stripped.includes("@g.us") ? stripped : `${stripped}@s.whatsapp.net`; - }, - isWhatsAppGroupJid: (value: string) => value.endsWith("@g.us"), - resolveWhatsAppOutboundTarget: ({ - to, - allowFrom, - mode, - }: { - to?: string; - allowFrom: string[]; - mode: "explicit" | "implicit"; - }) => { - const raw = typeof to === "string" ? to.trim() : ""; - if (!raw) { - return { ok: false, error: new Error("missing target") }; - } - const normalizeWhatsAppTarget = (value: string) => { - if (value === "invalid-target") return null; - const stripped = value.replace(/^whatsapp:/i, "").replace(/^\+/, ""); - return stripped.includes("@g.us") ? stripped : `${stripped}@s.whatsapp.net`; - }; - const normalized = normalizeWhatsAppTarget(raw); - if (!normalized) { - return { ok: false, error: new Error("invalid target") }; - } + }; - if (mode === "implicit" && !normalized.endsWith("@g.us")) { - const allowAll = allowFrom.includes("*"); - const allowExact = allowFrom.some((entry) => { - if (!entry) { - return false; - } - const normalizedEntry = normalizeWhatsAppTarget(entry.trim()); - return normalizedEntry?.toLowerCase() === normalized.toLowerCase(); - }); - if (!allowAll && !allowExact) { - return { ok: false, error: new Error("target not allowlisted") }; + return { + ...actual, + getChatChannelMeta: () => ({ id: "whatsapp", label: "WhatsApp" }), + normalizeWhatsAppTarget, + isWhatsAppGroupJid: (value: string) => value.endsWith("@g.us"), + resolveWhatsAppOutboundTarget: ({ + to, + allowFrom, + mode, + }: { + to?: string; + allowFrom: string[]; + mode: "explicit" | "implicit"; + }) => { + const raw = typeof to === "string" ? to.trim() : ""; + if (!raw) { + return { ok: false, error: new Error("missing target") }; + } + const normalized = normalizeWhatsAppTarget(raw); + if (!normalized) { + return { ok: false, error: new Error("invalid target") }; } - } - return { ok: true, to: normalized }; - }, - missingTargetError: (provider: string, hint: string) => - new Error(`Delivering to ${provider} requires target ${hint}`), - WhatsAppConfigSchema: {}, - whatsappOnboardingAdapter: {}, - resolveWhatsAppHeartbeatRecipients: vi.fn(), - buildChannelConfigSchema: vi.fn(), - collectWhatsAppStatusIssues: vi.fn(), - createActionGate: vi.fn(), - DEFAULT_ACCOUNT_ID: "default", - escapeRegExp: vi.fn(), - formatPairingApproveHint: vi.fn(), - listWhatsAppAccountIds: vi.fn(), - listWhatsAppDirectoryGroupsFromConfig: vi.fn(), - listWhatsAppDirectoryPeersFromConfig: vi.fn(), - looksLikeWhatsAppTargetId: vi.fn(), - migrateBaseNameToDefaultAccount: vi.fn(), - normalizeAccountId: vi.fn(), - normalizeE164: vi.fn(), - normalizeWhatsAppMessagingTarget: vi.fn(), - readStringParam: vi.fn(), - resolveDefaultWhatsAppAccountId: vi.fn(), - resolveWhatsAppAccount: vi.fn(), - resolveWhatsAppGroupIntroHint: vi.fn(), - resolveWhatsAppGroupRequireMention: vi.fn(), - resolveWhatsAppGroupToolPolicy: vi.fn(), - resolveWhatsAppMentionStripPatterns: vi.fn(() => []), - applyAccountNameToChannelSection: vi.fn(), -})); + if (mode === "implicit" && !normalized.endsWith("@g.us")) { + const allowAll = allowFrom.includes("*"); + const allowExact = allowFrom.some((entry) => { + if (!entry) { + return false; + } + const normalizedEntry = normalizeWhatsAppTarget(entry.trim()); + return normalizedEntry?.toLowerCase() === normalized.toLowerCase(); + }); + if (!allowAll && !allowExact) { + return { ok: false, error: new Error("target not allowlisted") }; + } + } + + return { ok: true, to: normalized }; + }, + missingTargetError: (provider: string, hint: string) => + new Error(`Delivering to ${provider} requires target ${hint}`), + }; +}); vi.mock("./runtime.js", () => ({ getWhatsAppRuntime: vi.fn(() => ({ diff --git a/extensions/whatsapp/src/runtime.ts b/extensions/whatsapp/src/runtime.ts index 7f79e3ef016..490c7873219 100644 --- a/extensions/whatsapp/src/runtime.ts +++ b/extensions/whatsapp/src/runtime.ts @@ -1,4 +1,4 @@ -import type { PluginRuntime } from "openclaw/plugin-sdk"; +import type { PluginRuntime } from "openclaw/plugin-sdk/whatsapp"; let runtime: PluginRuntime | null = null; diff --git a/extensions/zalo/index.ts b/extensions/zalo/index.ts index 2b8f11b0b1d..ccdc4aaacad 100644 --- a/extensions/zalo/index.ts +++ b/extensions/zalo/index.ts @@ -1,5 +1,5 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { zaloDock, zaloPlugin } from "./src/channel.js"; import { setZaloRuntime } from "./src/runtime.js"; diff --git a/extensions/zalouser/index.ts b/extensions/zalouser/index.ts index 0867197b995..6b5d470b85d 100644 --- a/extensions/zalouser/index.ts +++ b/extensions/zalouser/index.ts @@ -1,5 +1,5 @@ -import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk"; -import { emptyPluginConfigSchema } from "openclaw/plugin-sdk"; +import type { AnyAgentTool, OpenClawPluginApi } from "openclaw/plugin-sdk/core"; +import { emptyPluginConfigSchema } from "openclaw/plugin-sdk/core"; import { zalouserDock, zalouserPlugin } from "./src/channel.js"; import { setZalouserRuntime } from "./src/runtime.js"; import { ZalouserToolSchema, executeZalouserTool } from "./src/tool.js"; diff --git a/package.json b/package.json index 2b58d97c305..4e8c9bbc7e4 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,30 @@ "types": "./dist/plugin-sdk/telegram.d.ts", "default": "./dist/plugin-sdk/telegram.js" }, + "./plugin-sdk/discord": { + "types": "./dist/plugin-sdk/discord.d.ts", + "default": "./dist/plugin-sdk/discord.js" + }, + "./plugin-sdk/slack": { + "types": "./dist/plugin-sdk/slack.d.ts", + "default": "./dist/plugin-sdk/slack.js" + }, + "./plugin-sdk/signal": { + "types": "./dist/plugin-sdk/signal.d.ts", + "default": "./dist/plugin-sdk/signal.js" + }, + "./plugin-sdk/imessage": { + "types": "./dist/plugin-sdk/imessage.d.ts", + "default": "./dist/plugin-sdk/imessage.js" + }, + "./plugin-sdk/whatsapp": { + "types": "./dist/plugin-sdk/whatsapp.d.ts", + "default": "./dist/plugin-sdk/whatsapp.js" + }, + "./plugin-sdk/line": { + "types": "./dist/plugin-sdk/line.d.ts", + "default": "./dist/plugin-sdk/line.js" + }, "./plugin-sdk/account-id": { "types": "./dist/plugin-sdk/account-id.d.ts", "default": "./dist/plugin-sdk/account-id.js" @@ -71,7 +95,7 @@ "build:plugin-sdk:dts": "tsc -p tsconfig.plugin-sdk.dts.json", "build:strict-smoke": "pnpm canvas:a2ui:bundle && tsdown && pnpm build:plugin-sdk:dts", "canvas:a2ui:bundle": "bash scripts/bundle-a2ui.sh", - "check": "pnpm format:check && pnpm tsgo && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope && pnpm check:host-env-policy:swift", + "check": "pnpm format:check && pnpm tsgo && pnpm lint && pnpm lint:tmp:no-random-messaging && pnpm lint:tmp:channel-agnostic-boundaries && pnpm lint:tmp:no-raw-channel-fetch && pnpm lint:agent:ingress-owner && pnpm lint:plugins:no-register-http-handler && pnpm lint:plugins:no-monolithic-plugin-sdk-entry-imports && pnpm lint:webhook:no-low-level-body-read && pnpm lint:auth:no-pairing-store-group && pnpm lint:auth:pairing-account-scope && pnpm check:host-env-policy:swift", "check:docs": "pnpm format:docs:check && pnpm lint:docs && pnpm docs:check-links", "check:host-env-policy:swift": "node scripts/generate-host-env-security-policy-swift.mjs --check", "check:loc": "node --import tsx scripts/check-ts-max-loc.ts --max 500", @@ -115,6 +139,7 @@ "lint:docs": "pnpm dlx markdownlint-cli2", "lint:docs:fix": "pnpm dlx markdownlint-cli2 --fix", "lint:fix": "oxlint --type-aware --fix && pnpm format", + "lint:plugins:no-monolithic-plugin-sdk-entry-imports": "node --import tsx scripts/check-no-monolithic-plugin-sdk-entry-imports.ts", "lint:plugins:no-register-http-handler": "node scripts/check-no-register-http-handler.mjs", "lint:swift": "swiftlint lint --config .swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)", "lint:tmp:channel-agnostic-boundaries": "node scripts/check-channel-agnostic-boundaries.mjs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2e991dded9f..9cf76a8d161 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,13 +29,13 @@ importers: version: 3.1000.0 '@buape/carbon': specifier: 0.0.0-beta-20260216184201 - version: 0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1) + version: 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1) '@clack/prompts': specifier: ^1.0.1 version: 1.0.1 '@discordjs/voice': specifier: ^0.19.0 - version: 0.19.0(opusscript@0.1.1) + version: 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1) '@grammyjs/runner': specifier: ^2.0.3 version: 2.0.3(grammy@1.41.0) @@ -341,8 +341,8 @@ importers: specifier: ^10.6.1 version: 10.6.1 openclaw: - specifier: '>=2026.3.1' - version: 2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)) + specifier: '>=2026.3.2' + version: 2026.3.2(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)) extensions/imessage: {} @@ -402,8 +402,8 @@ importers: extensions/memory-core: dependencies: openclaw: - specifier: '>=2026.3.1' - version: 2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)) + specifier: '>=2026.3.2' + version: 2026.3.2(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)) extensions/memory-lancedb: dependencies: @@ -461,7 +461,7 @@ importers: dependencies: '@tloncorp/api': specifier: github:tloncorp/api-beta#7eede1c1a756977b09f96aa14a92e2b06318ae87 - version: https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87 + version: git+https://git@github.com:tloncorp/api-beta.git#7eede1c1a756977b09f96aa14a92e2b06318ae87 '@tloncorp/tlon-skill': specifier: 0.1.9 version: 0.1.9 @@ -925,6 +925,10 @@ packages: resolution: {integrity: sha512-YJOVVZ545x24mHzANfYoy0BJX5PDyeZlpiJjDkUBM/V/Ao7TFX9lcUvCN4nr0tbr5ubeaXxtEBILUrHtTphVeQ==} hasBin: true + '@discordjs/opus@0.10.0': + resolution: {integrity: sha512-HHEnSNrSPmFEyndRdQBJN2YE6egyXS9JUnJWyP6jficK0Y+qKMEZXyYTgmzpjrxXP1exM/hKaNP7BRBUEWkU5w==} + engines: {node: '>=12.0.0'} + '@discordjs/voice@0.19.0': resolution: {integrity: sha512-UyX6rGEXzVyPzb1yvjHtPfTlnLvB5jX/stAMdiytHhfoydX+98hfympdOwsnTktzr+IRvphxTbdErgYDJkEsvw==} engines: {node: '>=22.12.0'} @@ -2933,8 +2937,8 @@ packages: resolution: {integrity: sha512-5Kc5CM2Ysn3vTTArBs2vESUt0AQiWZA86yc1TI3B+lxXmtEq133C1nxXNOgnzhrivdPZIh3zLj5gDnZjoLL5GA==} engines: {node: '>=12.17.0'} - '@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87': - resolution: {tarball: https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87} + '@tloncorp/api@git+https://git@github.com:tloncorp/api-beta.git#7eede1c1a756977b09f96aa14a92e2b06318ae87': + resolution: {commit: 7eede1c1a756977b09f96aa14a92e2b06318ae87, repo: git@github.com:tloncorp/api-beta.git, type: git} version: 0.0.2 '@tloncorp/tlon-skill-darwin-arm64@0.1.9': @@ -4973,8 +4977,8 @@ packages: zod: optional: true - openclaw@2026.3.1: - resolution: {integrity: sha512-7Pt5ykhaYa8TYpLWnBhaMg6Lp6kfk3rMKgqJ3WWESKM9BizYu1fkH/rF9BLeXlsNASgZdLp4oR8H0XfvIIoXIg==} + openclaw@2026.3.2: + resolution: {integrity: sha512-Gkqx24m7PF1DUXPI968DuC9n52lTZ5hI3X5PIi0HosC7J7d6RLkgVppj1mxvgiQAWMp41E41elvoi/h4KBjFcQ==} engines: {node: '>=22.12.0'} hasBin: true peerDependencies: @@ -5185,10 +5189,13 @@ packages: prism-media@1.3.5: resolution: {integrity: sha512-IQdl0Q01m4LrkN1EGIE9lphov5Hy7WWlH6ulf5QdGePLlPas9p2mhgddTEHrlaXYjjFToM1/rWuwF37VF4taaA==} peerDependencies: + '@discordjs/opus': '>=0.8.0 <1.0.0' ffmpeg-static: ^5.0.2 || ^4.2.7 || ^3.0.0 || ^2.4.0 node-opus: ^0.3.3 opusscript: ^0.0.8 peerDependenciesMeta: + '@discordjs/opus': + optional: true ffmpeg-static: optional: true node-opus: @@ -6813,18 +6820,19 @@ snapshots: '@borewit/text-codec@0.2.1': {} - '@buape/carbon@0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1)': + '@buape/carbon@0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1)': dependencies: '@types/node': 25.3.3 discord-api-types: 0.38.37 optionalDependencies: '@cloudflare/workers-types': 4.20260120.0 - '@discordjs/voice': 0.19.0(opusscript@0.1.1) + '@discordjs/voice': 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1) '@hono/node-server': 1.19.9(hono@4.11.10) '@types/bun': 1.3.9 '@types/ws': 8.18.1 ws: 8.19.0 transitivePeerDependencies: + - '@discordjs/opus' - bufferutil - ffmpeg-static - hono @@ -6959,14 +6967,24 @@ snapshots: - supports-color optional: true - '@discordjs/voice@0.19.0(opusscript@0.1.1)': + '@discordjs/opus@0.10.0': + dependencies: + '@discordjs/node-pre-gyp': 0.4.5 + node-addon-api: 8.5.0 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + '@discordjs/voice@0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1)': dependencies: '@types/ws': 8.18.1 discord-api-types: 0.38.40 - prism-media: 1.3.5(opusscript@0.1.1) + prism-media: 1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1) tslib: 2.8.1 ws: 8.19.0 transitivePeerDependencies: + - '@discordjs/opus' - bufferutil - ffmpeg-static - node-opus @@ -8889,7 +8907,7 @@ snapshots: '@tinyhttp/content-disposition@2.2.4': {} - '@tloncorp/api@https://codeload.github.com/tloncorp/api-beta/tar.gz/7eede1c1a756977b09f96aa14a92e2b06318ae87': + '@tloncorp/api@git+https://git@github.com:tloncorp/api-beta.git#7eede1c1a756977b09f96aa14a92e2b06318ae87': dependencies: '@aws-sdk/client-s3': 3.1000.0 '@aws-sdk/s3-request-presigner': 3.1000.0 @@ -11171,13 +11189,13 @@ snapshots: ws: 8.19.0 zod: 4.3.6 - openclaw@2026.3.1(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)): + openclaw@2026.3.2(@napi-rs/canvas@0.1.95)(@types/express@5.0.6)(audio-decode@2.2.3)(hono@4.11.10)(node-llama-cpp@3.16.2(typescript@5.9.3)): dependencies: '@agentclientprotocol/sdk': 0.14.1(zod@4.3.6) '@aws-sdk/client-bedrock': 3.1000.0 - '@buape/carbon': 0.0.0-beta-20260216184201(hono@4.11.10)(opusscript@0.1.1) + '@buape/carbon': 0.0.0-beta-20260216184201(@discordjs/opus@0.10.0)(hono@4.11.10)(opusscript@0.1.1) '@clack/prompts': 1.0.1 - '@discordjs/voice': 0.19.0(opusscript@0.1.1) + '@discordjs/voice': 0.19.0(@discordjs/opus@0.10.0)(opusscript@0.1.1) '@grammyjs/runner': 2.0.3(grammy@1.41.0) '@grammyjs/transformer-throttler': 1.2.1(grammy@1.41.0) '@homebridge/ciao': 1.3.5 @@ -11226,12 +11244,15 @@ snapshots: qrcode-terminal: 0.12.0 sharp: 0.34.5 sqlite-vec: 0.1.7-alpha.2 + strip-ansi: 7.2.0 tar: 7.5.9 tslog: 4.10.2 undici: 7.22.0 ws: 8.19.0 yaml: 2.8.2 zod: 4.3.6 + optionalDependencies: + '@discordjs/opus': 0.10.0 transitivePeerDependencies: - '@modelcontextprotocol/sdk' - '@types/express' @@ -11485,8 +11506,9 @@ snapshots: dependencies: parse-ms: 4.0.0 - prism-media@1.3.5(opusscript@0.1.1): + prism-media@1.3.5(@discordjs/opus@0.10.0)(opusscript@0.1.1): optionalDependencies: + '@discordjs/opus': 0.10.0 opusscript: 0.1.1 process-nextick-args@2.0.1: {} diff --git a/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts new file mode 100644 index 00000000000..3c41add7ab6 --- /dev/null +++ b/scripts/check-no-monolithic-plugin-sdk-entry-imports.ts @@ -0,0 +1,50 @@ +import fs from "node:fs"; +import path from "node:path"; +import { discoverOpenClawPlugins } from "../src/plugins/discovery.js"; + +const ROOT_IMPORT_PATTERNS = [ + /\b(?:import|export)\b[\s\S]*?\bfrom\s+["']openclaw\/plugin-sdk["']/, + /\bimport\s+["']openclaw\/plugin-sdk["']/, + /\bimport\s*\(\s*["']openclaw\/plugin-sdk["']\s*\)/, + /\brequire\s*\(\s*["']openclaw\/plugin-sdk["']\s*\)/, +]; + +function hasMonolithicRootImport(content: string): boolean { + return ROOT_IMPORT_PATTERNS.some((pattern) => pattern.test(content)); +} + +function main() { + const discovery = discoverOpenClawPlugins({}); + const bundledEntryFiles = [ + ...new Set(discovery.candidates.filter((c) => c.origin === "bundled").map((c) => c.source)), + ]; + + const offenders: string[] = []; + for (const entryFile of bundledEntryFiles) { + let content = ""; + try { + content = fs.readFileSync(entryFile, "utf8"); + } catch { + continue; + } + if (hasMonolithicRootImport(content)) { + offenders.push(entryFile); + } + } + + if (offenders.length > 0) { + console.error("Bundled plugin entrypoints must not import monolithic openclaw/plugin-sdk."); + for (const file of offenders.toSorted()) { + const relative = path.relative(process.cwd(), file) || file; + console.error(`- ${relative}`); + } + console.error("Use openclaw/plugin-sdk/ for channel plugins or /core for others."); + process.exit(1); + } + + console.log( + `OK: bundled entrypoints use scoped plugin-sdk subpaths (${bundledEntryFiles.length} checked).`, + ); +} + +main(); diff --git a/scripts/check-plugin-sdk-exports.mjs b/scripts/check-plugin-sdk-exports.mjs index 51f58b8aa6b..993c92e33c3 100755 --- a/scripts/check-plugin-sdk-exports.mjs +++ b/scripts/check-plugin-sdk-exports.mjs @@ -41,6 +41,18 @@ const exportedNames = exportMatch[1] const exportSet = new Set(exportedNames); +const requiredSubpathEntries = [ + "core", + "telegram", + "discord", + "slack", + "signal", + "imessage", + "whatsapp", + "line", + "account-id", +]; + // Critical functions that channel extension plugins import from openclaw/plugin-sdk. // If any of these are missing, plugins will fail at runtime with: // TypeError: (0 , _pluginSdk.) is not a function @@ -76,10 +88,25 @@ for (const name of requiredExports) { } } +for (const entry of requiredSubpathEntries) { + const jsPath = resolve(__dirname, "..", "dist", "plugin-sdk", `${entry}.js`); + const dtsPath = resolve(__dirname, "..", "dist", "plugin-sdk", `${entry}.d.ts`); + if (!existsSync(jsPath)) { + console.error(`MISSING SUBPATH JS: dist/plugin-sdk/${entry}.js`); + missing += 1; + } + if (!existsSync(dtsPath)) { + console.error(`MISSING SUBPATH DTS: dist/plugin-sdk/${entry}.d.ts`); + missing += 1; + } +} + if (missing > 0) { - console.error(`\nERROR: ${missing} required export(s) missing from dist/plugin-sdk/index.js.`); + console.error( + `\nERROR: ${missing} required plugin-sdk artifact(s) missing (named exports or subpath files).`, + ); console.error("This will break channel extension plugins at runtime."); - console.error("Check src/plugin-sdk/index.ts and rebuild."); + console.error("Check src/plugin-sdk/index.ts, subpath entries, and rebuild."); process.exit(1); } diff --git a/scripts/release-check.ts b/scripts/release-check.ts index 03ceff6b94e..d4f302a824b 100755 --- a/scripts/release-check.ts +++ b/scripts/release-check.ts @@ -14,6 +14,22 @@ const requiredPathGroups = [ ["dist/entry.js", "dist/entry.mjs"], "dist/plugin-sdk/index.js", "dist/plugin-sdk/index.d.ts", + "dist/plugin-sdk/core.js", + "dist/plugin-sdk/core.d.ts", + "dist/plugin-sdk/telegram.js", + "dist/plugin-sdk/telegram.d.ts", + "dist/plugin-sdk/discord.js", + "dist/plugin-sdk/discord.d.ts", + "dist/plugin-sdk/slack.js", + "dist/plugin-sdk/slack.d.ts", + "dist/plugin-sdk/signal.js", + "dist/plugin-sdk/signal.d.ts", + "dist/plugin-sdk/imessage.js", + "dist/plugin-sdk/imessage.d.ts", + "dist/plugin-sdk/whatsapp.js", + "dist/plugin-sdk/whatsapp.d.ts", + "dist/plugin-sdk/line.js", + "dist/plugin-sdk/line.d.ts", "dist/build-info.json", ]; const forbiddenPrefixes = ["dist/OpenClaw.app/"]; diff --git a/scripts/write-plugin-sdk-entry-dts.ts b/scripts/write-plugin-sdk-entry-dts.ts index 58cea44ab21..611ec4dfe86 100644 --- a/scripts/write-plugin-sdk-entry-dts.ts +++ b/scripts/write-plugin-sdk-entry-dts.ts @@ -6,7 +6,18 @@ import path from "node:path"; // // Our package export map points subpath `types` at `dist/plugin-sdk/.d.ts`, so we // generate stable entry d.ts files that re-export the real declarations. -const entrypoints = ["index", "core", "telegram", "account-id"] as const; +const entrypoints = [ + "index", + "core", + "telegram", + "discord", + "slack", + "signal", + "imessage", + "whatsapp", + "line", + "account-id", +] as const; for (const entry of entrypoints) { const out = path.join(process.cwd(), `dist/plugin-sdk/${entry}.d.ts`); fs.mkdirSync(path.dirname(out), { recursive: true }); diff --git a/src/plugin-sdk/core.ts b/src/plugin-sdk/core.ts index 97960f925a0..d70ea17738f 100644 --- a/src/plugin-sdk/core.ts +++ b/src/plugin-sdk/core.ts @@ -1,8 +1,17 @@ -export type { OpenClawPluginApi, OpenClawPluginService } from "../plugins/types.js"; +export type { + AnyAgentTool, + OpenClawPluginApi, + OpenClawPluginService, + ProviderAuthContext, + ProviderAuthResult, +} from "../plugins/types.js"; export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawConfig } from "../config/config.js"; +export type { GatewayRequestHandlerOptions } from "../gateway/server-methods/types.js"; export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; +export { buildOauthProviderAuthResult } from "./provider-auth-result.js"; export { approveDevicePairing, @@ -15,6 +24,7 @@ export { type PluginCommandRunOptions, type PluginCommandRunResult, } from "./run-command.js"; +export { resolvePreferredOpenClawTmpDir } from "../infra/tmp-openclaw-dir.js"; export { resolveGatewayBindUrl } from "../shared/gateway-bind-url.js"; export type { GatewayBindUrlResult } from "../shared/gateway-bind-url.js"; diff --git a/src/plugin-sdk/discord.ts b/src/plugin-sdk/discord.ts new file mode 100644 index 00000000000..26a7b5c5031 --- /dev/null +++ b/src/plugin-sdk/discord.ts @@ -0,0 +1,60 @@ +export type { ChannelMessageActionAdapter } from "../channels/plugins/types.js"; +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { OpenClawConfig } from "../config/config.js"; +export type { ResolvedDiscordAccount } from "../discord/accounts.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; + +export { + applyAccountNameToChannelSection, + migrateBaseNameToDefaultAccount, +} from "../channels/plugins/setup-helpers.js"; +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { + deleteAccountFromConfigSection, + setAccountEnabledInConfigSection, +} from "../channels/plugins/config-helpers.js"; +export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; +export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; + +export { getChatChannelMeta } from "../channels/registry.js"; + +export { + listDiscordAccountIds, + resolveDefaultDiscordAccountId, + resolveDiscordAccount, +} from "../discord/accounts.js"; +export { + listDiscordDirectoryGroupsFromConfig, + listDiscordDirectoryPeersFromConfig, +} from "../channels/plugins/directory-config.js"; +export { + looksLikeDiscordTargetId, + normalizeDiscordMessagingTarget, + normalizeDiscordOutboundTarget, +} from "../channels/plugins/normalize/discord.js"; +export { collectDiscordAuditChannelIds } from "../discord/audit.js"; +export { collectDiscordStatusIssues } from "../channels/plugins/status-issues/discord.js"; + +export { + resolveDefaultGroupPolicy, + resolveOpenProviderRuntimeGroupPolicy, +} from "../config/runtime-group-policy.js"; +export { + resolveDiscordGroupRequireMention, + resolveDiscordGroupToolPolicy, +} from "../channels/plugins/group-mentions.js"; +export { discordOnboardingAdapter } from "../channels/plugins/onboarding/discord.js"; +export { DiscordConfigSchema } from "../config/zod-schema.providers-core.js"; + +export { + autoBindSpawnedDiscordSubagent, + listThreadBindingsBySessionKey, + unbindThreadBindingsBySessionKey, +} from "../discord/monitor/thread-bindings.js"; + +export { buildTokenChannelStatusSummary } from "./status-helpers.js"; diff --git a/src/plugin-sdk/imessage.ts b/src/plugin-sdk/imessage.ts new file mode 100644 index 00000000000..7e31560991d --- /dev/null +++ b/src/plugin-sdk/imessage.ts @@ -0,0 +1,49 @@ +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { ResolvedIMessageAccount } from "../imessage/accounts.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; + +export { + applyAccountNameToChannelSection, + migrateBaseNameToDefaultAccount, +} from "../channels/plugins/setup-helpers.js"; +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { + deleteAccountFromConfigSection, + setAccountEnabledInConfigSection, +} from "../channels/plugins/config-helpers.js"; +export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; +export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; + +export { getChatChannelMeta } from "../channels/registry.js"; +export { + listIMessageAccountIds, + resolveDefaultIMessageAccountId, + resolveIMessageAccount, +} from "../imessage/accounts.js"; +export { + formatTrimmedAllowFromEntries, + resolveIMessageConfigAllowFrom, + resolveIMessageConfigDefaultTo, +} from "./channel-config-helpers.js"; +export { + looksLikeIMessageTargetId, + normalizeIMessageMessagingTarget, +} from "../channels/plugins/normalize/imessage.js"; + +export { + resolveAllowlistProviderRuntimeGroupPolicy, + resolveDefaultGroupPolicy, +} from "../config/runtime-group-policy.js"; +export { + resolveIMessageGroupRequireMention, + resolveIMessageGroupToolPolicy, +} from "../channels/plugins/group-mentions.js"; +export { imessageOnboardingAdapter } from "../channels/plugins/onboarding/imessage.js"; +export { IMessageConfigSchema } from "../config/zod-schema.providers-core.js"; + +export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; diff --git a/src/plugin-sdk/line.ts b/src/plugin-sdk/line.ts new file mode 100644 index 00000000000..f7f6a3eeb37 --- /dev/null +++ b/src/plugin-sdk/line.ts @@ -0,0 +1,36 @@ +export type { + ChannelAccountSnapshot, + ChannelGatewayContext, + ChannelStatusIssue, +} from "../channels/plugins/types.js"; +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { OpenClawConfig } from "../config/config.js"; +export type { ReplyPayload } from "../auto-reply/types.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js"; + +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; + +export { + resolveAllowlistProviderRuntimeGroupPolicy, + resolveDefaultGroupPolicy, +} from "../config/runtime-group-policy.js"; + +export { buildTokenChannelStatusSummary } from "./status-helpers.js"; + +export { LineConfigSchema } from "../line/config-schema.js"; +export type { LineChannelData, LineConfig, ResolvedLineAccount } from "../line/types.js"; +export { + createActionCard, + createImageCard, + createInfoCard, + createListCard, + createReceiptCard, + type CardAction, + type ListItem, +} from "../line/flex-templates.js"; +export { processLineMessage } from "../line/markdown-to-line.js"; diff --git a/src/plugin-sdk/signal.ts b/src/plugin-sdk/signal.ts new file mode 100644 index 00000000000..d15d35ee1dc --- /dev/null +++ b/src/plugin-sdk/signal.ts @@ -0,0 +1,49 @@ +export type { ChannelMessageActionAdapter } from "../channels/plugins/types.js"; +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { ResolvedSignalAccount } from "../signal/accounts.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; + +export { + applyAccountNameToChannelSection, + migrateBaseNameToDefaultAccount, +} from "../channels/plugins/setup-helpers.js"; +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { + deleteAccountFromConfigSection, + setAccountEnabledInConfigSection, +} from "../channels/plugins/config-helpers.js"; +export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; +export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; + +export { getChatChannelMeta } from "../channels/registry.js"; +export { + listSignalAccountIds, + resolveDefaultSignalAccountId, + resolveSignalAccount, +} from "../signal/accounts.js"; +export { + looksLikeSignalTargetId, + normalizeSignalMessagingTarget, +} from "../channels/plugins/normalize/signal.js"; + +export { + resolveAllowlistProviderRuntimeGroupPolicy, + resolveDefaultGroupPolicy, +} from "../config/runtime-group-policy.js"; +export { signalOnboardingAdapter } from "../channels/plugins/onboarding/signal.js"; +export { SignalConfigSchema } from "../config/zod-schema.providers-core.js"; + +export { normalizeE164 } from "../utils.js"; +export { resolveChannelMediaMaxBytes } from "../channels/plugins/media-limits.js"; + +export { + buildBaseAccountStatusSnapshot, + buildBaseChannelStatusSummary, + collectStatusIssuesFromLastError, + createDefaultChannelRuntimeState, +} from "./status-helpers.js"; diff --git a/src/plugin-sdk/slack.ts b/src/plugin-sdk/slack.ts new file mode 100644 index 00000000000..af338f46b70 --- /dev/null +++ b/src/plugin-sdk/slack.ts @@ -0,0 +1,52 @@ +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { OpenClawConfig } from "../config/config.js"; +export type { ResolvedSlackAccount } from "../slack/accounts.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; + +export { + applyAccountNameToChannelSection, + migrateBaseNameToDefaultAccount, +} from "../channels/plugins/setup-helpers.js"; +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { + deleteAccountFromConfigSection, + setAccountEnabledInConfigSection, +} from "../channels/plugins/config-helpers.js"; +export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; +export { PAIRING_APPROVED_MESSAGE } from "../channels/plugins/pairing-message.js"; + +export { getChatChannelMeta } from "../channels/registry.js"; +export { + listSlackAccountIds, + resolveDefaultSlackAccountId, + resolveSlackAccount, + resolveSlackReplyToMode, +} from "../slack/accounts.js"; +export { + listSlackDirectoryGroupsFromConfig, + listSlackDirectoryPeersFromConfig, +} from "../channels/plugins/directory-config.js"; +export { + looksLikeSlackTargetId, + normalizeSlackMessagingTarget, +} from "../channels/plugins/normalize/slack.js"; +export { extractSlackToolSend, listSlackMessageActions } from "../slack/message-actions.js"; +export { buildSlackThreadingToolContext } from "../slack/threading-tool-context.js"; + +export { + resolveDefaultGroupPolicy, + resolveOpenProviderRuntimeGroupPolicy, +} from "../config/runtime-group-policy.js"; +export { + resolveSlackGroupRequireMention, + resolveSlackGroupToolPolicy, +} from "../channels/plugins/group-mentions.js"; +export { slackOnboardingAdapter } from "../channels/plugins/onboarding/slack.js"; +export { SlackConfigSchema } from "../config/zod-schema.providers-core.js"; + +export { handleSlackMessageAction } from "./slack-message-actions.js"; diff --git a/src/plugin-sdk/subpaths.test.ts b/src/plugin-sdk/subpaths.test.ts new file mode 100644 index 00000000000..80a2d2ffaf1 --- /dev/null +++ b/src/plugin-sdk/subpaths.test.ts @@ -0,0 +1,39 @@ +import * as discordSdk from "openclaw/plugin-sdk/discord"; +import * as imessageSdk from "openclaw/plugin-sdk/imessage"; +import * as lineSdk from "openclaw/plugin-sdk/line"; +import * as signalSdk from "openclaw/plugin-sdk/signal"; +import * as slackSdk from "openclaw/plugin-sdk/slack"; +import * as whatsappSdk from "openclaw/plugin-sdk/whatsapp"; +import { describe, expect, it } from "vitest"; + +describe("plugin-sdk subpath exports", () => { + it("exports Discord helpers", () => { + expect(typeof discordSdk.resolveDiscordAccount).toBe("function"); + expect(typeof discordSdk.discordOnboardingAdapter).toBe("object"); + }); + + it("exports Slack helpers", () => { + expect(typeof slackSdk.resolveSlackAccount).toBe("function"); + expect(typeof slackSdk.handleSlackMessageAction).toBe("function"); + }); + + it("exports Signal helpers", () => { + expect(typeof signalSdk.resolveSignalAccount).toBe("function"); + expect(typeof signalSdk.signalOnboardingAdapter).toBe("object"); + }); + + it("exports iMessage helpers", () => { + expect(typeof imessageSdk.resolveIMessageAccount).toBe("function"); + expect(typeof imessageSdk.imessageOnboardingAdapter).toBe("object"); + }); + + it("exports WhatsApp helpers", () => { + expect(typeof whatsappSdk.resolveWhatsAppAccount).toBe("function"); + expect(typeof whatsappSdk.whatsappOnboardingAdapter).toBe("object"); + }); + + it("exports LINE helpers", () => { + expect(typeof lineSdk.processLineMessage).toBe("function"); + expect(typeof lineSdk.createInfoCard).toBe("function"); + }); +}); diff --git a/src/plugin-sdk/whatsapp.ts b/src/plugin-sdk/whatsapp.ts new file mode 100644 index 00000000000..eaa9a890e8b --- /dev/null +++ b/src/plugin-sdk/whatsapp.ts @@ -0,0 +1,58 @@ +export type { ChannelMessageActionName } from "../channels/plugins/types.js"; +export type { ChannelPlugin } from "../channels/plugins/types.plugin.js"; +export type { ResolvedWhatsAppAccount } from "../web/accounts.js"; +export type { PluginRuntime } from "../plugins/runtime/types.js"; +export type { OpenClawPluginApi } from "../plugins/types.js"; + +export { emptyPluginConfigSchema } from "../plugins/config-schema.js"; + +export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; + +export { + applyAccountNameToChannelSection, + migrateBaseNameToDefaultAccount, +} from "../channels/plugins/setup-helpers.js"; +export { buildChannelConfigSchema } from "../channels/plugins/config-schema.js"; +export { formatPairingApproveHint } from "../channels/plugins/helpers.js"; + +export { getChatChannelMeta } from "../channels/registry.js"; +export { + listWhatsAppAccountIds, + resolveDefaultWhatsAppAccountId, + resolveWhatsAppAccount, +} from "../web/accounts.js"; +export { + formatWhatsAppConfigAllowFromEntries, + resolveWhatsAppConfigAllowFrom, + resolveWhatsAppConfigDefaultTo, +} from "./channel-config-helpers.js"; +export { + listWhatsAppDirectoryGroupsFromConfig, + listWhatsAppDirectoryPeersFromConfig, +} from "../channels/plugins/directory-config.js"; +export { + looksLikeWhatsAppTargetId, + normalizeWhatsAppMessagingTarget, +} from "../channels/plugins/normalize/whatsapp.js"; +export { resolveWhatsAppOutboundTarget } from "../whatsapp/resolve-outbound-target.js"; + +export { + resolveAllowlistProviderRuntimeGroupPolicy, + resolveDefaultGroupPolicy, +} from "../config/runtime-group-policy.js"; +export { + resolveWhatsAppGroupRequireMention, + resolveWhatsAppGroupToolPolicy, +} from "../channels/plugins/group-mentions.js"; +export { + resolveWhatsAppGroupIntroHint, + resolveWhatsAppMentionStripPatterns, +} from "../channels/plugins/whatsapp-shared.js"; +export { resolveWhatsAppHeartbeatRecipients } from "../channels/plugins/whatsapp-heartbeat.js"; +export { whatsappOnboardingAdapter } from "../channels/plugins/onboarding/whatsapp.js"; +export { collectWhatsAppStatusIssues } from "../channels/plugins/status-issues/whatsapp.js"; +export { WhatsAppConfigSchema } from "../config/zod-schema.providers-whatsapp.js"; + +export { createActionGate, readStringParam } from "../agents/tools/common.js"; + +export { normalizeE164 } from "../utils.js"; diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 1a002447711..1f9a6ebd5a5 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -1005,6 +1005,29 @@ describe("loadOpenClawPlugins", () => { expect(record?.status).toBe("loaded"); }); + it("supports legacy plugins importing monolithic plugin-sdk root", () => { + useNoBundledPlugins(); + const plugin = writePlugin({ + id: "legacy-root-import", + filename: "legacy-root-import.cjs", + body: `module.exports = { + id: "legacy-root-import", + configSchema: (require("openclaw/plugin-sdk").emptyPluginConfigSchema)(), + register() {}, +};`, + }); + + const registry = loadRegistryFromSinglePlugin({ + plugin, + pluginConfig: { + allow: ["legacy-root-import"], + }, + }); + + const record = registry.plugins.find((entry) => entry.id === "legacy-root-import"); + expect(record?.status).toBe("loaded"); + }); + it("prefers dist plugin-sdk alias when loader runs from dist", () => { const { root, distFile } = createPluginSdkAliasFixture(); diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 6bbdaacd5e0..8df588d6b87 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -100,6 +100,30 @@ const resolvePluginSdkTelegramAlias = (): string | null => { return resolvePluginSdkAliasFile({ srcFile: "telegram.ts", distFile: "telegram.js" }); }; +const resolvePluginSdkDiscordAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "discord.ts", distFile: "discord.js" }); +}; + +const resolvePluginSdkSlackAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "slack.ts", distFile: "slack.js" }); +}; + +const resolvePluginSdkSignalAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "signal.ts", distFile: "signal.js" }); +}; + +const resolvePluginSdkIMessageAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "imessage.ts", distFile: "imessage.js" }); +}; + +const resolvePluginSdkWhatsAppAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "whatsapp.ts", distFile: "whatsapp.js" }); +}; + +const resolvePluginSdkLineAlias = (): string | null => { + return resolvePluginSdkAliasFile({ srcFile: "line.ts", distFile: "line.js" }); +}; + export const __testing = { resolvePluginSdkAliasFile, }; @@ -478,10 +502,22 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi const pluginSdkAccountIdAlias = resolvePluginSdkAccountIdAlias(); const pluginSdkCoreAlias = resolvePluginSdkCoreAlias(); const pluginSdkTelegramAlias = resolvePluginSdkTelegramAlias(); + const pluginSdkDiscordAlias = resolvePluginSdkDiscordAlias(); + const pluginSdkSlackAlias = resolvePluginSdkSlackAlias(); + const pluginSdkSignalAlias = resolvePluginSdkSignalAlias(); + const pluginSdkIMessageAlias = resolvePluginSdkIMessageAlias(); + const pluginSdkWhatsAppAlias = resolvePluginSdkWhatsAppAlias(); + const pluginSdkLineAlias = resolvePluginSdkLineAlias(); const aliasMap = { ...(pluginSdkAlias ? { "openclaw/plugin-sdk": pluginSdkAlias } : {}), ...(pluginSdkCoreAlias ? { "openclaw/plugin-sdk/core": pluginSdkCoreAlias } : {}), ...(pluginSdkTelegramAlias ? { "openclaw/plugin-sdk/telegram": pluginSdkTelegramAlias } : {}), + ...(pluginSdkDiscordAlias ? { "openclaw/plugin-sdk/discord": pluginSdkDiscordAlias } : {}), + ...(pluginSdkSlackAlias ? { "openclaw/plugin-sdk/slack": pluginSdkSlackAlias } : {}), + ...(pluginSdkSignalAlias ? { "openclaw/plugin-sdk/signal": pluginSdkSignalAlias } : {}), + ...(pluginSdkIMessageAlias ? { "openclaw/plugin-sdk/imessage": pluginSdkIMessageAlias } : {}), + ...(pluginSdkWhatsAppAlias ? { "openclaw/plugin-sdk/whatsapp": pluginSdkWhatsAppAlias } : {}), + ...(pluginSdkLineAlias ? { "openclaw/plugin-sdk/line": pluginSdkLineAlias } : {}), ...(pluginSdkAccountIdAlias ? { "openclaw/plugin-sdk/account-id": pluginSdkAccountIdAlias } : {}), diff --git a/tsconfig.plugin-sdk.dts.json b/tsconfig.plugin-sdk.dts.json index 4deee810315..3e5be344b80 100644 --- a/tsconfig.plugin-sdk.dts.json +++ b/tsconfig.plugin-sdk.dts.json @@ -14,6 +14,12 @@ "src/plugin-sdk/index.ts", "src/plugin-sdk/core.ts", "src/plugin-sdk/telegram.ts", + "src/plugin-sdk/discord.ts", + "src/plugin-sdk/slack.ts", + "src/plugin-sdk/signal.ts", + "src/plugin-sdk/imessage.ts", + "src/plugin-sdk/whatsapp.ts", + "src/plugin-sdk/line.ts", "src/plugin-sdk/account-id.ts", "src/plugin-sdk/keyed-async-queue.ts", "src/types/**/*.d.ts" diff --git a/tsdown.config.ts b/tsdown.config.ts index 819396b2feb..ef5fd70dbb9 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -69,6 +69,48 @@ export default defineConfig([ fixedExtension: false, platform: "node", }, + { + entry: "src/plugin-sdk/discord.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, + { + entry: "src/plugin-sdk/slack.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, + { + entry: "src/plugin-sdk/signal.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, + { + entry: "src/plugin-sdk/imessage.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, + { + entry: "src/plugin-sdk/whatsapp.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, + { + entry: "src/plugin-sdk/line.ts", + outDir: "dist/plugin-sdk", + env, + fixedExtension: false, + platform: "node", + }, { entry: "src/plugin-sdk/account-id.ts", outDir: "dist/plugin-sdk", diff --git a/vitest.config.ts b/vitest.config.ts index e95927ae22f..026b1a618f2 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -25,6 +25,30 @@ export default defineConfig({ find: "openclaw/plugin-sdk/telegram", replacement: path.join(repoRoot, "src", "plugin-sdk", "telegram.ts"), }, + { + find: "openclaw/plugin-sdk/discord", + replacement: path.join(repoRoot, "src", "plugin-sdk", "discord.ts"), + }, + { + find: "openclaw/plugin-sdk/slack", + replacement: path.join(repoRoot, "src", "plugin-sdk", "slack.ts"), + }, + { + find: "openclaw/plugin-sdk/signal", + replacement: path.join(repoRoot, "src", "plugin-sdk", "signal.ts"), + }, + { + find: "openclaw/plugin-sdk/imessage", + replacement: path.join(repoRoot, "src", "plugin-sdk", "imessage.ts"), + }, + { + find: "openclaw/plugin-sdk/whatsapp", + replacement: path.join(repoRoot, "src", "plugin-sdk", "whatsapp.ts"), + }, + { + find: "openclaw/plugin-sdk/line", + replacement: path.join(repoRoot, "src", "plugin-sdk", "line.ts"), + }, { find: "openclaw/plugin-sdk/keyed-async-queue", replacement: path.join(repoRoot, "src", "plugin-sdk", "keyed-async-queue.ts"),