From 1c66a050c26a4b8a02550eb003aa2929b37efb6c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 3 Apr 2026 19:47:25 +0100 Subject: [PATCH] refactor(plugins): move outbound dep aliases into extensions --- extensions/discord/api.ts | 4 ++++ extensions/imessage/api.ts | 1 + extensions/imessage/src/channel.runtime.ts | 5 ++++- extensions/imessage/src/outbound-adapter.ts | 5 ++++- extensions/imessage/src/outbound-send-deps.ts | 1 + extensions/slack/api.ts | 4 ++++ extensions/telegram/api.ts | 4 ++++ extensions/whatsapp/api.ts | 1 + extensions/whatsapp/src/outbound-adapter.ts | 11 +++++++---- extensions/whatsapp/src/outbound-base.ts | 9 +++++++-- extensions/whatsapp/src/outbound-send-deps.ts | 1 + src/cli/outbound-send-mapping.test.ts | 6 +++--- src/cli/outbound-send-mapping.ts | 10 ++-------- src/infra/outbound/send-deps.ts | 18 +++++++++--------- 14 files changed, 52 insertions(+), 28 deletions(-) create mode 100644 extensions/imessage/src/outbound-send-deps.ts create mode 100644 extensions/whatsapp/src/outbound-send-deps.ts diff --git a/extensions/discord/api.ts b/extensions/discord/api.ts index 5f5f2a593ae..d31597d1bdc 100644 --- a/extensions/discord/api.ts +++ b/extensions/discord/api.ts @@ -6,6 +6,10 @@ export * from "./src/components.js"; export * from "./src/directory-config.js"; export * from "./src/exec-approvals.js"; export * from "./src/group-policy.js"; +export type { + DiscordInteractiveHandlerContext, + DiscordInteractiveHandlerRegistration, +} from "./src/interactive-dispatch.js"; export * from "./src/normalize.js"; export * from "./src/pluralkit.js"; export * from "./src/probe.js"; diff --git a/extensions/imessage/api.ts b/extensions/imessage/api.ts index aa9f064e8a7..160ee349eac 100644 --- a/extensions/imessage/api.ts +++ b/extensions/imessage/api.ts @@ -2,6 +2,7 @@ export * from "./src/accounts.js"; export * from "./src/conversation-bindings.js"; export * from "./src/conversation-id.js"; export * from "./src/group-policy.js"; +export { IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./src/outbound-send-deps.js"; export * from "./src/probe.js"; export * from "./src/target-parsing-helpers.js"; export * from "./src/targets.js"; diff --git a/extensions/imessage/src/channel.runtime.ts b/extensions/imessage/src/channel.runtime.ts index 9cbdce77e65..edb7acc7622 100644 --- a/extensions/imessage/src/channel.runtime.ts +++ b/extensions/imessage/src/channel.runtime.ts @@ -2,6 +2,7 @@ import { resolveOutboundSendDep } from "openclaw/plugin-sdk/outbound-runtime"; import { PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes } from "../runtime-api.js"; import type { ResolvedIMessageAccount } from "./accounts.js"; import { monitorIMessageProvider } from "./monitor.js"; +import { IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js"; import { probeIMessage } from "./probe.js"; import { sendMessageIMessage } from "./send.js"; import { imessageSetupWizard } from "./setup-surface.js"; @@ -19,7 +20,9 @@ export async function sendIMessageOutbound(params: { replyToId?: string; }) { const send = - resolveOutboundSendDep(params.deps, "imessage") ?? sendMessageIMessage; + resolveOutboundSendDep(params.deps, "imessage", { + legacyKeys: IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? sendMessageIMessage; const maxBytes = resolveChannelMediaMaxBytes({ cfg: params.cfg, resolveChannelLimitMb: ({ cfg, accountId }) => diff --git a/extensions/imessage/src/outbound-adapter.ts b/extensions/imessage/src/outbound-adapter.ts index a344841f8fb..14f2596a0f1 100644 --- a/extensions/imessage/src/outbound-adapter.ts +++ b/extensions/imessage/src/outbound-adapter.ts @@ -6,11 +6,14 @@ import { resolveOutboundSendDep, type OutboundSendDeps, } from "openclaw/plugin-sdk/outbound-runtime"; +import { IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js"; import { sendMessageIMessage } from "./send.js"; function resolveIMessageSender(deps: OutboundSendDeps | undefined) { return ( - resolveOutboundSendDep(deps, "imessage") ?? sendMessageIMessage + resolveOutboundSendDep(deps, "imessage", { + legacyKeys: IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? sendMessageIMessage ); } diff --git a/extensions/imessage/src/outbound-send-deps.ts b/extensions/imessage/src/outbound-send-deps.ts new file mode 100644 index 00000000000..19c814e7373 --- /dev/null +++ b/extensions/imessage/src/outbound-send-deps.ts @@ -0,0 +1 @@ +export const IMESSAGE_LEGACY_OUTBOUND_SEND_DEP_KEYS = ["sendIMessage"] as const; diff --git a/extensions/slack/api.ts b/extensions/slack/api.ts index 67e4d430c7d..0a80058aed8 100644 --- a/extensions/slack/api.ts +++ b/extensions/slack/api.ts @@ -8,6 +8,10 @@ export * from "./src/channel-type.js"; export * from "./src/client.js"; export * from "./src/directory-config.js"; export * from "./src/http/index.js"; +export type { + SlackInteractiveHandlerContext, + SlackInteractiveHandlerRegistration, +} from "./src/interactive-dispatch.js"; export * from "./src/interactive-replies.js"; export * from "./src/message-actions.js"; export * from "./src/group-policy.js"; diff --git a/extensions/telegram/api.ts b/extensions/telegram/api.ts index 5c623049397..e4d187b85c5 100644 --- a/extensions/telegram/api.ts +++ b/extensions/telegram/api.ts @@ -9,6 +9,10 @@ export * from "./src/directory-config.js"; export * from "./src/exec-approval-forwarding.js"; export * from "./src/exec-approvals.js"; export * from "./src/group-policy.js"; +export type { + TelegramInteractiveHandlerContext, + TelegramInteractiveHandlerRegistration, +} from "./src/interactive-dispatch.js"; export * from "./src/inline-buttons.js"; export * from "./src/model-buttons.js"; export * from "./src/normalize.js"; diff --git a/extensions/whatsapp/api.ts b/extensions/whatsapp/api.ts index 5991e2a3d91..e5a50c2e5ee 100644 --- a/extensions/whatsapp/api.ts +++ b/extensions/whatsapp/api.ts @@ -2,6 +2,7 @@ export * from "./src/accounts.js"; export * from "./src/auto-reply/constants.js"; export { whatsappCommandPolicy } from "./src/command-policy.js"; export * from "./src/group-policy.js"; +export { WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./src/outbound-send-deps.js"; export type * from "./src/auto-reply/types.js"; export type * from "./src/inbound/types.js"; export { diff --git a/extensions/whatsapp/src/outbound-adapter.ts b/extensions/whatsapp/src/outbound-adapter.ts index 8943440f8a7..124c8516dbf 100644 --- a/extensions/whatsapp/src/outbound-adapter.ts +++ b/extensions/whatsapp/src/outbound-adapter.ts @@ -10,6 +10,7 @@ import { } from "openclaw/plugin-sdk/reply-payload"; import { chunkText } from "openclaw/plugin-sdk/reply-runtime"; import { shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; +import { WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js"; import { resolveWhatsAppOutboundTarget } from "./runtime-api.js"; import { sendMessageWhatsApp, sendPollWhatsApp } from "./send.js"; @@ -52,8 +53,9 @@ export const whatsappOutbound: ChannelOutboundAdapter = { return createEmptyChannelResult("whatsapp"); } const send = - resolveOutboundSendDep(deps, "whatsapp") ?? - (await import("./send.js")).sendMessageWhatsApp; + resolveOutboundSendDep(deps, "whatsapp", { + legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? (await import("./send.js")).sendMessageWhatsApp; return await send(to, normalizedText, { verbose: false, cfg, @@ -74,8 +76,9 @@ export const whatsappOutbound: ChannelOutboundAdapter = { }) => { const normalizedText = trimLeadingWhitespace(text); const send = - resolveOutboundSendDep(deps, "whatsapp") ?? - (await import("./send.js")).sendMessageWhatsApp; + resolveOutboundSendDep(deps, "whatsapp", { + legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? (await import("./send.js")).sendMessageWhatsApp; return await send(to, normalizedText, { verbose: false, cfg, diff --git a/extensions/whatsapp/src/outbound-base.ts b/extensions/whatsapp/src/outbound-base.ts index 026ddaadc3a..e6433992f91 100644 --- a/extensions/whatsapp/src/outbound-base.ts +++ b/extensions/whatsapp/src/outbound-base.ts @@ -4,6 +4,7 @@ import { } from "openclaw/plugin-sdk/channel-send-result"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime"; import { resolveOutboundSendDep, sanitizeForPlainText } from "openclaw/plugin-sdk/infra-runtime"; +import { WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS } from "./outbound-send-deps.js"; type WhatsAppChunker = NonNullable; type WhatsAppSendTextOptions = { @@ -77,7 +78,9 @@ export function createWhatsAppOutboundBase({ return { messageId: "" }; } const send = - resolveOutboundSendDep(deps, "whatsapp") ?? sendMessageWhatsApp; + resolveOutboundSendDep(deps, "whatsapp", { + legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? sendMessageWhatsApp; return await send(to, normalizedText, { verbose: false, cfg, @@ -98,7 +101,9 @@ export function createWhatsAppOutboundBase({ gifPlayback, }) => { const send = - resolveOutboundSendDep(deps, "whatsapp") ?? sendMessageWhatsApp; + resolveOutboundSendDep(deps, "whatsapp", { + legacyKeys: WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS, + }) ?? sendMessageWhatsApp; return await send(to, normalizeText(text), { verbose: false, cfg, diff --git a/extensions/whatsapp/src/outbound-send-deps.ts b/extensions/whatsapp/src/outbound-send-deps.ts new file mode 100644 index 00000000000..ad877de4ce0 --- /dev/null +++ b/extensions/whatsapp/src/outbound-send-deps.ts @@ -0,0 +1 @@ +export const WHATSAPP_LEGACY_OUTBOUND_SEND_DEP_KEYS = ["sendWhatsApp"] as const; diff --git a/src/cli/outbound-send-mapping.test.ts b/src/cli/outbound-send-mapping.test.ts index 4d68d9ce249..2ec49f407a9 100644 --- a/src/cli/outbound-send-mapping.test.ts +++ b/src/cli/outbound-send-mapping.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import { createOutboundSendDepsFromCliSource } from "./outbound-send-mapping.js"; describe("createOutboundSendDepsFromCliSource", () => { - it("adds legacy aliases for channel-keyed send deps", () => { + it("adds generic legacy aliases for channel-keyed send deps", () => { const deps = { whatsapp: vi.fn(), telegram: vi.fn(), @@ -21,12 +21,12 @@ describe("createOutboundSendDepsFromCliSource", () => { slack: deps.slack, signal: deps.signal, imessage: deps.imessage, - sendWhatsApp: deps.whatsapp, + sendWhatsapp: deps.whatsapp, sendTelegram: deps.telegram, sendDiscord: deps.discord, sendSlack: deps.slack, sendSignal: deps.signal, - sendIMessage: deps.imessage, + sendImessage: deps.imessage, }); }); }); diff --git a/src/cli/outbound-send-mapping.ts b/src/cli/outbound-send-mapping.ts index 0c2e5fe18a7..e6dcd8f4228 100644 --- a/src/cli/outbound-send-mapping.ts +++ b/src/cli/outbound-send-mapping.ts @@ -32,14 +32,8 @@ function resolveLegacyDepKeysForChannel(channelId: string): string[] { } const pascal = compact.charAt(0).toUpperCase() + compact.slice(1); const keys = new Set(); - if (compact === "whatsapp") { - keys.add("sendWhatsApp"); - } else if (compact === "imessage") { - keys.add("sendIMessage"); - } else { - keys.add(`send${pascal}`); - } - if (compact !== "imessage" && pascal.startsWith("I") && pascal.length > 1) { + keys.add(`send${pascal}`); + if (pascal.startsWith("I") && pascal.length > 1) { keys.add(`sendI${pascal.slice(1)}`); } if (pascal.startsWith("Ms") && pascal.length > 2) { diff --git a/src/infra/outbound/send-deps.ts b/src/infra/outbound/send-deps.ts index 925d82b02e9..80d91adf589 100644 --- a/src/infra/outbound/send-deps.ts +++ b/src/infra/outbound/send-deps.ts @@ -12,14 +12,8 @@ function resolveLegacyDepKeysForChannel(channelId: string): string[] { } const pascal = compact.charAt(0).toUpperCase() + compact.slice(1); const keys = new Set(); - if (compact === "whatsapp") { - keys.add("sendWhatsApp"); - } else if (compact === "imessage") { - keys.add("sendIMessage"); - } else { - keys.add(`send${pascal}`); - } - if (compact !== "imessage" && pascal.startsWith("I") && pascal.length > 1) { + keys.add(`send${pascal}`); + if (pascal.startsWith("I") && pascal.length > 1) { keys.add(`sendI${pascal.slice(1)}`); } if (pascal.startsWith("Ms") && pascal.length > 2) { @@ -28,15 +22,21 @@ function resolveLegacyDepKeysForChannel(channelId: string): string[] { return [...keys]; } +export type ResolveOutboundSendDepOptions = { + legacyKeys?: readonly string[]; +}; + export function resolveOutboundSendDep( deps: OutboundSendDeps | null | undefined, channelId: string, + options?: ResolveOutboundSendDepOptions, ): T | undefined { const dynamic = deps?.[channelId]; if (dynamic !== undefined) { return dynamic as T; } - for (const legacyKey of resolveLegacyDepKeysForChannel(channelId)) { + const legacyKeys = [...resolveLegacyDepKeysForChannel(channelId), ...(options?.legacyKeys ?? [])]; + for (const legacyKey of legacyKeys) { const legacy = deps?.[legacyKey]; if (legacy !== undefined) { return legacy as T;