refactor(plugin-sdk): remove channel-specific sdk seams

This commit is contained in:
Peter Steinberger
2026-04-03 10:42:27 +01:00
parent ad6fdf1e3c
commit f2d7a825b1
118 changed files with 709 additions and 2199 deletions

View File

@@ -16,3 +16,4 @@ export * from "./src/resolve-channels.js";
export * from "./src/resolve-users.js";
export * from "./src/outbound-session-route.js";
export * from "./src/send.js";
export * from "./src/send.components.js";

View File

@@ -14,6 +14,7 @@ import {
withNormalizedTimestamp,
readBooleanParam,
} from "../runtime-api.js";
import { sendDiscordComponentMessage } from "../send.components.js";
import {
createThreadDiscord,
deleteMessageDiscord,
@@ -29,7 +30,6 @@ import {
removeOwnReactionsDiscord,
removeReactionDiscord,
searchMessagesDiscord,
sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
sendStickerDiscord,

View File

@@ -1,8 +1,8 @@
import type { GatewayPlugin } from "@buape/carbon/gateway";
import type { DiscordActionConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ActionGate } from "openclaw/plugin-sdk/discord-core";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { clearGateways, registerGateway } from "../monitor/gateway-registry.js";
import type { ActionGate } from "../runtime-api.js";
import { handleDiscordPresenceAction } from "./runtime.presence.js";
const mockUpdatePresence = vi.fn();

View File

@@ -1,5 +1,5 @@
import { type OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter.js";

View File

@@ -3,7 +3,7 @@ import { isChannelExecApprovalClientEnabledFromConfig } from "openclaw/plugin-sd
import { resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordExecApprovalConfig } from "openclaw/plugin-sdk/config-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { resolveDiscordAccount } from "./accounts.js";
import { parseDiscordTarget } from "./targets.js";

View File

@@ -25,25 +25,24 @@ import {
} from "openclaw/plugin-sdk/channel-inbound";
import { enqueueSystemEvent } from "openclaw/plugin-sdk/channel-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import {
type PluginInteractiveDiscordHandlerContext,
} from "openclaw/plugin-sdk/plugin-runtime";
import { type PluginInteractiveDiscordHandlerContext } from "openclaw/plugin-sdk/plugin-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { createNonExitingRuntime, type RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { readSessionUpdatedAt, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
import { logDebug, logError } from "openclaw/plugin-sdk/text-runtime";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
import {
parseDiscordComponentCustomIdForCarbon,
parseDiscordModalCustomIdForCarbon,
} from "../component-custom-id.js";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
import { resolveDiscordComponentEntry, resolveDiscordModalEntry } from "../components-registry.js";
import type { DiscordComponentEntry, DiscordModalEntry } from "../components.js";
import { editDiscordComponentMessage } from "../send.components.js";
import {
AGENT_BUTTON_KEY,
AGENT_SELECT_KEY,
@@ -96,7 +95,6 @@ let replyRuntimePromise: Promise<typeof import("openclaw/plugin-sdk/reply-runtim
let replyPipelineRuntimePromise:
| Promise<typeof import("openclaw/plugin-sdk/channel-reply-pipeline")>
| undefined;
let sendComponentsRuntimePromise: Promise<typeof import("../send.components.js")> | undefined;
let typingRuntimePromise: Promise<typeof import("./typing.js")> | undefined;
async function loadConversationRuntime() {
@@ -124,11 +122,6 @@ async function loadReplyPipelineRuntime() {
return await replyPipelineRuntimePromise;
}
async function loadSendComponentsRuntime() {
sendComponentsRuntimePromise ??= import("../send.components.js");
return await sendComponentsRuntimePromise;
}
async function loadTypingRuntime() {
typingRuntimePromise ??= import("./typing.js");
return await typingRuntimePromise;
@@ -269,7 +262,6 @@ async function dispatchPluginDiscordInteractiveEvent(params: {
const approvalMessageId = params.messageId?.trim() || params.interaction.message?.id?.trim();
if (approvalMessageId) {
try {
const { editDiscordComponentMessage } = await loadSendComponentsRuntime();
await editDiscordComponentMessage(
normalizedConversationId,
approvalMessageId,
@@ -901,7 +893,9 @@ async function handleDiscordModalTrigger(params: {
}
try {
await params.interaction.showModal((await loadComponentsRuntime()).createDiscordFormModal(modalEntry));
await params.interaction.showModal(
(await loadComponentsRuntime()).createDiscordFormModal(modalEntry),
);
} catch (err) {
logError(`${params.label}: failed to show modal: ${String(err)}`);
}

View File

@@ -1,4 +1,4 @@
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";
export function buildFinalizedDiscordDirectInboundContext() {

View File

@@ -1,4 +1,4 @@
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { expectChannelInboundContextContract as expectInboundContextContract } from "openclaw/plugin-sdk/testing";
import { describe, expect, it } from "vitest";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";

View File

@@ -23,12 +23,12 @@ import { recordInboundSession } from "openclaw/plugin-sdk/conversation-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import {
buildPendingHistoryContextFromMap,
clearHistoryEntriesIfEnabled,
} from "openclaw/plugin-sdk/reply-history";
import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
import { resolveThreadSessionKeys } from "openclaw/plugin-sdk/routing";
import { danger, logVerbose, shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env";

View File

@@ -1,5 +1,5 @@
import type { CommandArgs } from "openclaw/plugin-sdk/command-auth";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-runtime";
import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { type DiscordChannelConfigResolved, type DiscordGuildEntryResolved } from "./allow-list.js";
import { buildDiscordInboundAccessContext } from "./inbound-context.js";

View File

@@ -3,7 +3,7 @@ import type { NativeCommandSpec } from "openclaw/plugin-sdk/command-auth";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { DiscordAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import * as pluginCommandsModule from "openclaw/plugin-sdk/plugin-runtime";
import * as dispatcherModule from "openclaw/plugin-sdk/reply-runtime";
import * as dispatcherModule from "openclaw/plugin-sdk/reply-dispatch-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { __testing as nativeCommandTesting, createDiscordNativeCommand } from "./native-command.js";
import {

View File

@@ -3,7 +3,7 @@ import * as commandRegistryModule from "openclaw/plugin-sdk/command-auth";
import type { ChatCommandDefinition, CommandArgsParsing } from "openclaw/plugin-sdk/command-auth";
import type { ModelsProviderData } from "openclaw/plugin-sdk/command-auth";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import * as dispatcherModule from "openclaw/plugin-sdk/reply-runtime";
import * as dispatcherModule from "openclaw/plugin-sdk/reply-dispatch-runtime";
import * as globalsModule from "openclaw/plugin-sdk/runtime-env";
import * as commandTextModule from "openclaw/plugin-sdk/text-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";

View File

@@ -17,6 +17,10 @@ import {
resolveCommandAuthorizedFromAuthorizers,
resolveNativeCommandSessionTargets,
} from "openclaw/plugin-sdk/command-auth-native";
import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { buildPairingReply } from "openclaw/plugin-sdk/conversation-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import {
buildCommandTextFromArgs,
findCommandByNativeName,
@@ -31,21 +35,19 @@ import {
type CommandArgs,
type NativeCommandSpec,
} from "openclaw/plugin-sdk/native-command-registry";
import type { OpenClawConfig, loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { buildPairingReply } from "openclaw/plugin-sdk/conversation-runtime";
import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
import * as pluginRuntime from "openclaw/plugin-sdk/plugin-runtime";
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
import {
dispatchReplyWithDispatcher,
type ReplyPayload,
} from "openclaw/plugin-sdk/reply-dispatch-runtime";
import {
resolveSendableOutboundReplyParts,
resolveTextChunksWithFallback,
} from "openclaw/plugin-sdk/reply-payload";
import * as replyRuntime from "openclaw/plugin-sdk/reply-runtime";
import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordMaxLinesPerMessage } from "../accounts.js";
import { chunkDiscordTextWithMode } from "../chunk.js";
@@ -87,7 +89,7 @@ const log = createSubsystemLogger("discord/native-command");
const DISCORD_COMMAND_DESCRIPTION_MAX = 100;
let matchPluginCommandImpl = pluginRuntime.matchPluginCommand;
let executePluginCommandImpl = pluginRuntime.executePluginCommand;
let dispatchReplyWithDispatcherImpl = replyRuntime.dispatchReplyWithDispatcher;
let dispatchReplyWithDispatcherImpl = dispatchReplyWithDispatcher;
let resolveDiscordNativeInteractionRouteStateImpl = resolveDiscordNativeInteractionRouteState;
export const __testing = {
@@ -106,8 +108,8 @@ export const __testing = {
return previous;
},
setDispatchReplyWithDispatcher(
next: typeof replyRuntime.dispatchReplyWithDispatcher,
): typeof replyRuntime.dispatchReplyWithDispatcher {
next: typeof dispatchReplyWithDispatcher,
): typeof dispatchReplyWithDispatcher {
const previous = dispatchReplyWithDispatcherImpl;
dispatchReplyWithDispatcherImpl = next;
return previous;

View File

@@ -22,7 +22,7 @@ import type { OpenClawConfig, ReplyToMode } from "openclaw/plugin-sdk/config-run
import { loadConfig } from "openclaw/plugin-sdk/config-runtime";
import { createConnectedChannelStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
import { getPluginCommandSpecs } from "openclaw/plugin-sdk/plugin-runtime";
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-runtime";
import { resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
import {
danger,
isVerbose,

View File

@@ -2,13 +2,13 @@ import type { RequestClient } from "@buape/carbon";
import { resolveAgentAvatar } from "openclaw/plugin-sdk/agent-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { MarkdownTableMode, ReplyToMode } from "openclaw/plugin-sdk/config-runtime";
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-dispatch-runtime";
import {
resolveSendableOutboundReplyParts,
resolveTextChunksWithFallback,
sendMediaWithLeadingCaption,
} from "openclaw/plugin-sdk/reply-payload";
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime";
import {
resolveRetryConfig,
retryAsync,

View File

@@ -5,7 +5,7 @@ import {
type OpenClawConfig,
type ReplyToMode,
} from "openclaw/plugin-sdk/config-runtime";
import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-runtime";
import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-reference";
import { buildAgentSessionKey } from "openclaw/plugin-sdk/routing";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-runtime";

View File

@@ -16,6 +16,7 @@ export function createDiscordOutboundHoisted() {
}
type DiscordSendModule = typeof import("./send.js");
type DiscordSendComponentsModule = typeof import("./send.components.js");
type DiscordThreadBindingsModule = typeof import("./monitor/thread-bindings.js");
export const DEFAULT_DISCORD_SEND_RESULT = {
@@ -34,14 +35,24 @@ export async function createDiscordSendModuleMock(
return {
...actual,
sendMessageDiscord: (...args: unknown[]) => hoisted.sendMessageDiscordMock(...args),
sendDiscordComponentMessage: (...args: unknown[]) =>
hoisted.sendDiscordComponentMessageMock(...args),
sendPollDiscord: (...args: unknown[]) => hoisted.sendPollDiscordMock(...args),
sendWebhookMessageDiscord: (...args: unknown[]) =>
hoisted.sendWebhookMessageDiscordMock(...args),
};
}
export async function createDiscordSendComponentsModuleMock(
hoisted: DiscordOutboundHoisted,
importOriginal: () => Promise<DiscordSendComponentsModule>,
) {
const actual = await importOriginal();
return {
...actual,
sendDiscordComponentMessage: (...args: unknown[]) =>
hoisted.sendDiscordComponentMessageMock(...args),
};
}
export async function createDiscordThreadBindingsModuleMock(
hoisted: DiscordOutboundHoisted,
importOriginal: () => Promise<DiscordThreadBindingsModule>,
@@ -59,14 +70,20 @@ export async function installDiscordOutboundModuleSpies(hoisted: DiscordOutbound
vi.spyOn(sendModule, "sendMessageDiscord").mockImplementation(
mockedSendModule.sendMessageDiscord,
);
vi.spyOn(sendModule, "sendDiscordComponentMessage").mockImplementation(
mockedSendModule.sendDiscordComponentMessage,
);
vi.spyOn(sendModule, "sendPollDiscord").mockImplementation(mockedSendModule.sendPollDiscord);
vi.spyOn(sendModule, "sendWebhookMessageDiscord").mockImplementation(
mockedSendModule.sendWebhookMessageDiscord,
);
const sendComponentsModule = await import("./send.components.js");
const mockedSendComponentsModule = await createDiscordSendComponentsModuleMock(
hoisted,
async () => sendComponentsModule,
);
vi.spyOn(sendComponentsModule, "sendDiscordComponentMessage").mockImplementation(
mockedSendComponentsModule.sendDiscordComponentMessage,
);
const threadBindingsModule = await import("./monitor/thread-bindings.js");
const mockedThreadBindingsModule = await createDiscordThreadBindingsModuleMock(
hoisted,

View File

@@ -16,12 +16,8 @@ import {
import type { DiscordComponentMessageSpec } from "./components.js";
import { getThreadBindingManager, type ThreadBindingRecord } from "./monitor/thread-bindings.js";
import { normalizeDiscordOutboundTarget } from "./normalize.js";
import {
sendDiscordComponentMessage,
sendMessageDiscord,
sendPollDiscord,
sendWebhookMessageDiscord,
} from "./send.js";
import { sendDiscordComponentMessage } from "./send.components.js";
import { sendMessageDiscord, sendPollDiscord, sendWebhookMessageDiscord } from "./send.js";
import { buildDiscordInteractiveComponents } from "./shared-interactive.js";
export const DISCORD_TEXT_CHUNK_LIMIT = 2000;

View File

@@ -7,27 +7,35 @@ export {
} from "openclaw/plugin-sdk/channel-status";
export {
buildChannelConfigSchema,
getChatChannelMeta,
DiscordConfigSchema,
} from "openclaw/plugin-sdk/channel-config-schema";
export type {
ChannelMessageActionAdapter,
ChannelMessageActionContext,
ChannelMessageActionName,
} from "openclaw/plugin-sdk/channel-contract";
export type { ChannelPlugin, OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core";
export type {
DiscordAccountConfig,
DiscordActionConfig,
DiscordConfig,
OpenClawConfig,
} from "openclaw/plugin-sdk/config-runtime";
export {
jsonResult,
readNumberParam,
readStringArrayParam,
readStringParam,
resolvePollMaxSelections,
type ActionGate,
type ChannelPlugin,
type DiscordAccountConfig,
type DiscordActionConfig,
type DiscordConfig,
type OpenClawConfig,
} from "openclaw/plugin-sdk/discord-core";
export { DiscordConfigSchema } from "openclaw/plugin-sdk/discord-core";
} from "openclaw/plugin-sdk/channel-actions";
export type { ActionGate } from "openclaw/plugin-sdk/channel-actions";
export { readBooleanParam } from "openclaw/plugin-sdk/boolean-param";
export {
assertMediaNotDataUrl,
parseAvailableTags,
readReactionParams,
withNormalizedTimestamp,
} from "openclaw/plugin-sdk/discord-core";
} from "openclaw/plugin-sdk/channel-actions";
export {
createHybridChannelConfigAdapter,
createScopedChannelConfigAdapter,
@@ -40,12 +48,13 @@ export {
createAccountListHelpers,
} from "openclaw/plugin-sdk/account-helpers";
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/discord";
export {
emptyPluginConfigSchema,
formatPairingApproveHint,
getChatChannelMeta,
} from "openclaw/plugin-sdk/core";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
export { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
export type {
ChannelMessageActionAdapter,
ChannelMessageActionName,
} from "openclaw/plugin-sdk/channel-contract";
export {
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,

View File

@@ -10,7 +10,7 @@ import { maxBytesForKind } from "openclaw/plugin-sdk/media-runtime";
import { extensionForMime } from "openclaw/plugin-sdk/media-runtime";
import { unlinkIfExists } from "openclaw/plugin-sdk/media-runtime";
import type { PollInput } from "openclaw/plugin-sdk/media-runtime";
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-runtime";
import { resolveChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import type { RetryConfig } from "openclaw/plugin-sdk/retry-runtime";
import { resolvePreferredOpenClawTmpDir } from "openclaw/plugin-sdk/temp-path";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";

View File

@@ -17,8 +17,8 @@ import {
normalizePollInput,
type PollInput,
} from "openclaw/plugin-sdk/media-runtime";
import type { ChunkMode } from "openclaw/plugin-sdk/reply-chunking";
import { resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
import type { ChunkMode } from "openclaw/plugin-sdk/reply-runtime";
import type { RetryRunner } from "openclaw/plugin-sdk/retry-runtime";
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
import { resolveDiscordAccount } from "./accounts.js";

View File

@@ -44,11 +44,6 @@ export {
sendWebhookMessageDiscord,
sendVoiceMessageDiscord,
} from "./send.outbound.js";
export {
editDiscordComponentMessage,
registerBuiltDiscordComponentMessage,
sendDiscordComponentMessage,
} from "./send.components.js";
export { sendTypingDiscord } from "./send.typing.js";
export {
fetchChannelPermissionsDiscord,

View File

@@ -1,7 +1,7 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import type { Mock } from "vitest";
import { expect, vi } from "vitest";
import type { OpenClawConfig } from "../../../../src/plugin-sdk/discord.js";
export type NativeCommandSpecMock = {
name: string;

View File

@@ -4,19 +4,32 @@ export {
projectCredentialSnapshotFields,
resolveConfiguredFromRequiredCredentialStatuses,
} from "openclaw/plugin-sdk/channel-status";
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/slack";
export { looksLikeSlackTargetId, normalizeSlackMessagingTarget } from "./targets.js";
export type { ChannelPlugin, OpenClawConfig, SlackAccountConfig } from "openclaw/plugin-sdk/slack";
export {
buildChannelConfigSchema,
SlackConfigSchema,
} from "openclaw/plugin-sdk/channel-config-schema";
export type { ChannelMessageActionContext } from "openclaw/plugin-sdk/channel-contract";
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
export type {
ChannelPlugin,
OpenClawConfig,
OpenClawPluginApi,
PluginRuntime,
} from "openclaw/plugin-sdk/core";
export type { SlackAccountConfig } from "openclaw/plugin-sdk/config-runtime";
export {
emptyPluginConfigSchema,
formatPairingApproveHint,
getChatChannelMeta,
} from "openclaw/plugin-sdk/core";
export { loadOutboundMediaFromUrl } from "openclaw/plugin-sdk/outbound-media";
export { looksLikeSlackTargetId, normalizeSlackMessagingTarget } from "./targets.js";
export {
createActionGate,
imageResultFromFile,
jsonResult,
readNumberParam,
readReactionParams,
readStringParam,
SlackConfigSchema,
withNormalizedTimestamp,
} from "openclaw/plugin-sdk/slack-core";
} from "openclaw/plugin-sdk/channel-actions";

View File

@@ -1,19 +1,12 @@
export type {
ChannelMessageActionAdapter,
ChannelPlugin,
OpenClawConfig,
OpenClawPluginApi,
PluginRuntime,
TelegramAccountConfig,
TelegramActionConfig,
TelegramNetworkConfig,
} from "openclaw/plugin-sdk/telegram-core";
export type { OpenClawPluginApi, PluginRuntime } from "openclaw/plugin-sdk/core";
export type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
export type { TelegramApiOverride } from "./src/send.js";
export type {
OpenClawPluginService,
OpenClawPluginServiceContext,
PluginLogger,
} from "openclaw/plugin-sdk/core";
import type { OpenClawConfig as RuntimeOpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export type {
AcpRuntime,
AcpRuntimeCapabilities,
@@ -29,19 +22,23 @@ export type {
export { AcpRuntimeError } from "openclaw/plugin-sdk/acp-runtime";
export {
buildTokenChannelStatusSummary,
clearAccountEntryFields,
DEFAULT_ACCOUNT_ID,
normalizeAccountId,
PAIRING_APPROVED_MESSAGE,
parseTelegramTopicConversation,
projectCredentialSnapshotFields,
resolveConfiguredFromCredentialStatuses,
resolveTelegramPollVisibility,
} from "openclaw/plugin-sdk/telegram-core";
emptyPluginConfigSchema,
formatPairingApproveHint,
getChatChannelMeta,
} from "openclaw/plugin-sdk/core";
export {
buildChannelConfigSchema,
getChatChannelMeta,
TelegramConfigSchema,
} from "openclaw/plugin-sdk/channel-config-schema";
export { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
export {
PAIRING_APPROVED_MESSAGE,
buildTokenChannelStatusSummary,
projectCredentialSnapshotFields,
resolveConfiguredFromCredentialStatuses,
} from "openclaw/plugin-sdk/channel-status";
export {
jsonResult,
readNumberParam,
readReactionParams,
@@ -49,8 +46,7 @@ export {
readStringOrNumberParam,
readStringParam,
resolvePollMaxSelections,
TelegramConfigSchema,
} from "openclaw/plugin-sdk/telegram-core";
} from "openclaw/plugin-sdk/channel-actions";
export type { TelegramProbe } from "./src/probe.js";
export { auditTelegramGroupMembership, collectTelegramUnmentionedGroupIds } from "./src/audit.js";
export { resolveTelegramRuntimeGroupPolicy } from "./src/group-access.js";
@@ -90,3 +86,12 @@ export {
setTelegramThreadBindingMaxAgeBySessionKey,
} from "./src/thread-bindings.js";
export { resolveTelegramToken } from "./src/token.js";
export type { ChannelPlugin } from "openclaw/plugin-sdk/core";
export type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export type TelegramAccountConfig = NonNullable<
NonNullable<RuntimeOpenClawConfig["channels"]>["telegram"]
>;
export type TelegramActionConfig = NonNullable<TelegramAccountConfig["actions"]>;
export type TelegramNetworkConfig = NonNullable<TelegramAccountConfig["network"]>;
export { parseTelegramTopicConversation } from "./src/topic-conversation.js";
export { resolveTelegramPollVisibility } from "./src/poll-visibility.js";

View File

@@ -1,6 +1,7 @@
import { resolveAccountWithDefaultFallback } from "openclaw/plugin-sdk/account-core";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { coerceSecretRef } from "openclaw/plugin-sdk/config-runtime";
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
import { resolveDefaultSecretProviderAlias } from "openclaw/plugin-sdk/provider-auth";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
@@ -8,7 +9,6 @@ import {
hasConfiguredSecretInput,
normalizeSecretInputString,
} from "openclaw/plugin-sdk/secret-input";
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/telegram-core";
import {
mergeTelegramAccountConfig,
resolveDefaultTelegramAccountId,

View File

@@ -10,16 +10,16 @@ import {
resolveAccountWithDefaultFallback,
type OpenClawConfig,
} from "openclaw/plugin-sdk/account-core";
import type {
TelegramAccountConfig,
TelegramActionConfig,
} from "openclaw/plugin-sdk/config-runtime";
import {
listBoundAccountIds,
resolveDefaultAgentBoundAccountId,
} from "openclaw/plugin-sdk/routing";
import { formatSetExplicitDefaultInstruction } from "openclaw/plugin-sdk/routing";
import { createSubsystemLogger, isTruthyEnvValue } from "openclaw/plugin-sdk/runtime-env";
import type {
TelegramAccountConfig,
TelegramActionConfig,
} from "openclaw/plugin-sdk/telegram-core";
import { resolveTelegramToken } from "./token.js";
let log: ReturnType<typeof createSubsystemLogger> | null = null;

View File

@@ -9,9 +9,8 @@ import {
readStringOrNumberParam,
readStringParam,
resolvePollMaxSelections,
type OpenClawConfig,
type TelegramActionConfig,
} from "openclaw/plugin-sdk/telegram-core";
} from "openclaw/plugin-sdk/channel-actions";
import type { OpenClawConfig, TelegramActionConfig } from "openclaw/plugin-sdk/config-runtime";
import { createTelegramActionGate, resolveTelegramPollActionGateState } from "./accounts.js";
import {
fitsTelegramCallbackData,
@@ -23,6 +22,7 @@ import {
resolveTelegramInlineButtonsScope,
resolveTelegramTargetChatType,
} from "./inline-buttons.js";
import { resolveTelegramPollVisibility } from "./poll-visibility.js";
import { resolveTelegramReactionLevel } from "./reaction-level.js";
import {
createForumTopicTelegram,
@@ -94,22 +94,6 @@ function readTelegramForumTopicIconColor(
}
return iconColor as TelegramForumTopicIconColor;
}
function resolveTelegramPollVisibility(params: {
pollAnonymous?: boolean;
pollPublic?: boolean;
}): boolean | undefined {
if (params.pollAnonymous && params.pollPublic) {
throw new Error("pollAnonymous and pollPublic are mutually exclusive");
}
if (params.pollAnonymous) {
return true;
}
if (params.pollPublic) {
return false;
}
return undefined;
}
export function readTelegramButtons(
params: Record<string, unknown>,
): TelegramInlineButtons | undefined {

View File

@@ -1,4 +1,4 @@
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveTelegramApiBase, resolveTelegramFetch } from "./fetch.js";
import { makeProxyFetch } from "./proxy.js";

View File

@@ -29,11 +29,6 @@ import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-sna
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { getChildLogger } from "openclaw/plugin-sdk/runtime-env";
import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env";
import {
normalizeTelegramCommandName,
resolveTelegramCustomCommands,
TELEGRAM_COMMAND_NAME_PATTERN,
} from "openclaw/plugin-sdk/telegram-command-config";
import { resolveTelegramAccount } from "./accounts.js";
import { withTelegramApiErrorLogging } from "./api-logging.js";
import { isSenderAllowed, normalizeDmAllowFromWithStore } from "./bot-access.js";
@@ -59,6 +54,11 @@ import {
} from "./bot/helpers.js";
import type { TelegramContext, TelegramGetChat } from "./bot/types.js";
import type { TelegramInlineButtons } from "./button-types.js";
import {
normalizeTelegramCommandName,
resolveTelegramCustomCommands,
TELEGRAM_COMMAND_NAME_PATTERN,
} from "./command-config.js";
import {
resolveTelegramConversationBaseSessionKey,
resolveTelegramConversationRoute,

View File

@@ -1,4 +1,4 @@
import type { ChannelPlugin } from "openclaw/plugin-sdk/telegram-core";
import type { ChannelPlugin } from "openclaw/plugin-sdk/core";
import { type ResolvedTelegramAccount } from "./accounts.js";
import type { TelegramProbe } from "./probe.js";
import { telegramSetupAdapter } from "./setup-core.js";

View File

@@ -1,11 +1,21 @@
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
import {
buildDmGroupAccountAllowlistAdapter,
createNestedAllowlistOverrideResolver,
} from "openclaw/plugin-sdk/allowlist-config-edit";
import type { ChannelMessageActionAdapter } from "openclaw/plugin-sdk/channel-contract";
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
import { createAllowlistProviderRouteAllowlistWarningCollector } from "openclaw/plugin-sdk/channel-policy";
import { attachChannelToResult } from "openclaw/plugin-sdk/channel-send-result";
import {
PAIRING_APPROVED_MESSAGE,
buildTokenChannelStatusSummary,
projectCredentialSnapshotFields,
resolveConfiguredFromCredentialStatuses,
} from "openclaw/plugin-sdk/channel-status";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { createChatChannelPlugin } from "openclaw/plugin-sdk/core";
import { clearAccountEntryFields } from "openclaw/plugin-sdk/core";
import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
import {
resolveOutboundSendDep,
@@ -22,17 +32,6 @@ import {
createComputedAccountStatusAdapter,
createDefaultChannelRuntimeState,
} from "openclaw/plugin-sdk/status-helpers";
import {
buildTokenChannelStatusSummary,
clearAccountEntryFields,
DEFAULT_ACCOUNT_ID,
PAIRING_APPROVED_MESSAGE,
parseTelegramTopicConversation,
projectCredentialSnapshotFields,
resolveConfiguredFromCredentialStatuses,
type ChannelMessageActionAdapter,
type OpenClawConfig,
} from "openclaw/plugin-sdk/telegram-core";
import {
listTelegramAccountIds,
resolveTelegramAccount,
@@ -89,6 +88,7 @@ import {
} from "./thread-bindings.js";
import { buildTelegramThreadingToolContext } from "./threading-tool-context.js";
import { resolveTelegramToken } from "./token.js";
import { parseTelegramTopicConversation } from "./topic-conversation.js";
type TelegramSendFn = typeof sendMessageTelegram;
@@ -824,8 +824,7 @@ export const telegramPlugin = createChatChannelPlugin({
threading: {
topLevelReplyToMode: "telegram",
buildToolContext: (params) => buildTelegramThreadingToolContext(params),
resolveAutoThreadId: ({ to, toolContext }) =>
resolveTelegramAutoThreadId({ to, toolContext }),
resolveAutoThreadId: ({ to, toolContext }) => resolveTelegramAutoThreadId({ to, toolContext }),
},
outbound: {
base: {

View File

@@ -0,0 +1,95 @@
export const TELEGRAM_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
export type TelegramCustomCommandInput = {
command?: string | null;
description?: string | null;
};
export type TelegramCustomCommandIssue = {
index: number;
field: "command" | "description";
message: string;
};
export function normalizeTelegramCommandName(value: string): string {
const trimmed = value.trim();
if (!trimmed) {
return "";
}
const withoutSlash = trimmed.startsWith("/") ? trimmed.slice(1) : trimmed;
return withoutSlash.trim().toLowerCase().replace(/-/g, "_");
}
function normalizeTelegramCommandDescription(value: string): string {
return value.trim();
}
export function resolveTelegramCustomCommands(params: {
commands?: TelegramCustomCommandInput[] | null;
reservedCommands?: Set<string>;
checkReserved?: boolean;
checkDuplicates?: boolean;
}): {
commands: Array<{ command: string; description: string }>;
issues: TelegramCustomCommandIssue[];
} {
const entries = Array.isArray(params.commands) ? params.commands : [];
const reserved = params.reservedCommands ?? new Set<string>();
const checkReserved = params.checkReserved !== false;
const checkDuplicates = params.checkDuplicates !== false;
const seen = new Set<string>();
const resolved: Array<{ command: string; description: string }> = [];
const issues: TelegramCustomCommandIssue[] = [];
for (let index = 0; index < entries.length; index += 1) {
const entry = entries[index];
const normalized = normalizeTelegramCommandName(String(entry?.command ?? ""));
if (!normalized) {
issues.push({
index,
field: "command",
message: "Telegram custom command is missing a command name.",
});
continue;
}
if (!TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
issues.push({
index,
field: "command",
message: `Telegram custom command "/${normalized}" is invalid (use a-z, 0-9, underscore; max 32 chars).`,
});
continue;
}
if (checkReserved && reserved.has(normalized)) {
issues.push({
index,
field: "command",
message: `Telegram custom command "/${normalized}" conflicts with a native command.`,
});
continue;
}
if (checkDuplicates && seen.has(normalized)) {
issues.push({
index,
field: "command",
message: `Telegram custom command "/${normalized}" is duplicated.`,
});
continue;
}
const description = normalizeTelegramCommandDescription(String(entry?.description ?? ""));
if (!description) {
issues.push({
index,
field: "description",
message: `Telegram custom command "/${normalized}" is missing a description.`,
});
continue;
}
if (checkDuplicates) {
seen.add(normalized);
}
resolved.push({ command: normalized, description });
}
return { commands: resolved, issues };
}

View File

@@ -0,0 +1,15 @@
export function resolveTelegramPollVisibility(params: {
pollAnonymous?: boolean;
pollPublic?: boolean;
}): boolean | undefined {
if (params.pollAnonymous && params.pollPublic) {
throw new Error("pollAnonymous and pollPublic are mutually exclusive");
}
if (params.pollAnonymous) {
return true;
}
if (params.pollPublic) {
return false;
}
return undefined;
}

View File

@@ -1,5 +1,5 @@
import type { BaseProbeResult } from "openclaw/plugin-sdk/channel-contract";
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
import { fetchWithTimeout } from "openclaw/plugin-sdk/text-runtime";
import { resolveTelegramApiBase, resolveTelegramFetch } from "./fetch.js";
import { makeProxyFetch } from "./proxy.js";

View File

@@ -1,4 +1,4 @@
import { parseTelegramTopicConversation } from "openclaw/plugin-sdk/telegram-core";
import { parseTelegramTopicConversation } from "./topic-conversation.js";
export function resolveTelegramSessionConversation(params: {
kind: "group" | "channel";

View File

@@ -1,3 +1,4 @@
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/config-runtime";
import {
createEnvPatchedAccountSetupAdapter,
DEFAULT_ACCOUNT_ID,
@@ -9,7 +10,6 @@ import {
} from "openclaw/plugin-sdk/setup";
import type { ChannelSetupAdapter, ChannelSetupDmPolicy } from "openclaw/plugin-sdk/setup";
import { formatCliCommand, formatDocsLink } from "openclaw/plugin-sdk/setup-tools";
import type { TelegramNetworkConfig } from "openclaw/plugin-sdk/telegram-core";
import { resolveDefaultTelegramAccountId, resolveTelegramAccount } from "./accounts.js";
import { lookupTelegramChatId } from "./api-fetch.js";

View File

@@ -4,14 +4,14 @@ import {
adaptScopedAccountAccessor,
createScopedChannelConfigAdapter,
} from "openclaw/plugin-sdk/channel-config-helpers";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
import {
getChatChannelMeta,
normalizeAccountId,
type ChannelPlugin,
type OpenClawConfig,
} from "openclaw/plugin-sdk/telegram-core";
} from "openclaw/plugin-sdk/core";
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/routing";
import { inspectTelegramAccount } from "./account-inspect.js";
import {
listTelegramAccountIds,

View File

@@ -1,10 +1,10 @@
import { resolveNormalizedAccountEntry } from "openclaw/plugin-sdk/account-core";
import type { BaseTokenResolution } from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/config-runtime";
import { tryReadSecretFileSync } from "openclaw/plugin-sdk/core";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/routing";
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
import type { TelegramAccountConfig } from "openclaw/plugin-sdk/telegram-core";
export type TelegramTokenSource = "env" | "tokenFile" | "config" | "none";

View File

@@ -0,0 +1,58 @@
export type ParsedTelegramTopicConversation = {
chatId: string;
topicId: string;
canonicalConversationId: string;
};
function buildTelegramTopicConversationId(params: {
chatId: string;
topicId: string;
}): string | null {
const chatId = params.chatId.trim();
const topicId = params.topicId.trim();
if (!/^-?\d+$/.test(chatId) || !/^\d+$/.test(topicId)) {
return null;
}
return `${chatId}:topic:${topicId}`;
}
export function parseTelegramTopicConversation(params: {
conversationId: string;
parentConversationId?: string;
}): ParsedTelegramTopicConversation | null {
const conversation = params.conversationId.trim();
const directMatch = conversation.match(/^(-?\d+):topic:(\d+)$/i);
if (directMatch?.[1] && directMatch[2]) {
const canonicalConversationId = buildTelegramTopicConversationId({
chatId: directMatch[1],
topicId: directMatch[2],
});
if (!canonicalConversationId) {
return null;
}
return {
chatId: directMatch[1],
topicId: directMatch[2],
canonicalConversationId,
};
}
if (!/^\d+$/.test(conversation)) {
return null;
}
const parent = params.parentConversationId?.trim();
if (!parent || !/^-?\d+$/.test(parent)) {
return null;
}
const canonicalConversationId = buildTelegramTopicConversationId({
chatId: parent,
topicId: conversation,
});
if (!canonicalConversationId) {
return null;
}
return {
chatId: parent,
topicId: conversation,
canonicalConversationId,
};
}

View File

@@ -1,6 +1,6 @@
import { buildDmGroupAccountAllowlistAdapter } from "openclaw/plugin-sdk/allowlist-config-edit";
import { splitChannelApprovalCapability } from "openclaw/plugin-sdk/approval-runtime";
import { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/telegram-core";
import { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";
import type { ResolvedTelegramAccount } from "./src/accounts.js";
import { resolveTelegramAccount } from "./src/accounts.js";
import { telegramApprovalCapability } from "./src/approval-native.js";

View File

@@ -1 +1,70 @@
export { hasAnyWhatsAppAuth } from "./src/accounts.js";
import fs from "node:fs";
import path from "node:path";
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "openclaw/plugin-sdk/account-id";
import { resolveUserPath } from "openclaw/plugin-sdk/account-resolution";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { resolveOAuthDir } from "openclaw/plugin-sdk/state-paths";
import { hasWebCredsSync } from "./src/creds-files.js";
function addAccountAuthDirs(
authDirs: Set<string>,
accountId: string,
authDir: string | undefined,
accountsRoot: string,
env: NodeJS.ProcessEnv,
): void {
authDirs.add(path.join(accountsRoot, normalizeAccountId(accountId)));
const configuredAuthDir = authDir?.trim();
if (configuredAuthDir) {
authDirs.add(resolveUserPath(configuredAuthDir, env));
}
}
function listWhatsAppAuthDirs(
cfg: OpenClawConfig,
env: NodeJS.ProcessEnv = process.env,
): readonly string[] {
const oauthDir = resolveOAuthDir(env);
const accountsRoot = path.join(oauthDir, "whatsapp");
const channel = cfg.channels?.whatsapp;
const authDirs = new Set<string>([oauthDir, path.join(accountsRoot, DEFAULT_ACCOUNT_ID)]);
addAccountAuthDirs(authDirs, DEFAULT_ACCOUNT_ID, undefined, accountsRoot, env);
if (channel?.defaultAccount?.trim()) {
addAccountAuthDirs(
authDirs,
channel.defaultAccount,
channel.accounts?.[channel.defaultAccount]?.authDir,
accountsRoot,
env,
);
}
const accounts = channel?.accounts;
if (accounts) {
for (const [accountId, account] of Object.entries(accounts)) {
addAccountAuthDirs(authDirs, accountId, account?.authDir, accountsRoot, env);
}
}
try {
const entries = fs.readdirSync(accountsRoot, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
authDirs.add(path.join(accountsRoot, entry.name));
}
}
} catch {
// Missing directories mean no auth state.
}
return [...authDirs];
}
export function hasAnyWhatsAppAuth(
cfg: OpenClawConfig,
env: NodeJS.ProcessEnv = process.env,
): boolean {
return listWhatsAppAuthDirs(cfg, env).some((authDir) => hasWebCredsSync(authDir));
}

View File

@@ -1,35 +1,39 @@
export { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core";
export {
buildChannelConfigSchema,
createActionGate,
DEFAULT_ACCOUNT_ID,
WhatsAppConfigSchema,
} from "openclaw/plugin-sdk/channel-config-schema";
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
export {
formatWhatsAppConfigAllowFromEntries,
getChatChannelMeta,
resolveWhatsAppConfigAllowFrom,
resolveWhatsAppConfigDefaultTo,
} from "openclaw/plugin-sdk/channel-config-helpers";
export {
createActionGate,
jsonResult,
normalizeE164,
readReactionParams,
readStringParam,
resolveWhatsAppGroupIntroHint,
resolveWhatsAppGroupRequireMention,
resolveWhatsAppGroupToolPolicy,
ToolAuthorizationError,
WhatsAppConfigSchema,
type ChannelPlugin,
type OpenClawConfig,
} from "openclaw/plugin-sdk/whatsapp-core";
} from "openclaw/plugin-sdk/channel-actions";
export { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/config-runtime";
import type { OpenClawConfig as RuntimeOpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
export {
type ChannelMessageActionName,
createWhatsAppOutboundBase,
looksLikeWhatsAppTargetId,
normalizeWhatsAppAllowFromEntries,
normalizeWhatsAppMessagingTarget,
resolveWhatsAppGroupIntroHint,
resolveWhatsAppHeartbeatRecipients,
resolveWhatsAppMentionStripRegexes,
type ChannelMessageActionName,
type DmPolicy,
type GroupPolicy,
type WhatsAppAccountConfig,
} from "openclaw/plugin-sdk/whatsapp-shared";
} from "openclaw/plugin-sdk/channel-runtime";
import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
export {
resolveWhatsAppGroupRequireMention,
resolveWhatsAppGroupToolPolicy,
} from "./group-policy.js";
export {
isWhatsAppGroupJid,
isWhatsAppUserTarget,
@@ -37,6 +41,19 @@ export {
} from "./normalize-target.js";
export { resolveWhatsAppOutboundTarget } from "./resolve-outbound-target.js";
export { resolveWhatsAppReactionLevel } from "./reaction-level.js";
export type OpenClawConfig = RuntimeOpenClawConfig;
export type WhatsAppAccountConfig = NonNullable<
NonNullable<NonNullable<RuntimeOpenClawConfig["channels"]>["whatsapp"]>["accounts"]
>[string];
export class ToolAuthorizationError extends Error {
constructor(message: string) {
super(message);
this.name = "ToolAuthorizationError";
}
}
type MonitorWebChannel = typeof import("./channel.runtime.js").monitorWebChannel;
let channelRuntimePromise: Promise<typeof import("./channel.runtime.js")> | null = null;